@nmnmcc/ability 2.1.1 → 2.1.2

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.
@@ -1 +1 @@
1
- {"version":3,"file":"Policy.js","names":["RuleData.builder","RuleData.makePlain","Internal.PolicyError","Internal.define","Internal.combine","Internal.ceiling","Internal.provideContext","Decide.decideAny","Decide.decideSync","Decide.explain","Decide.permittedFields","Analysis.residual","Analysis.coverageAny","Internal.schema"],"sources":["../src/Policy.ts"],"sourcesContent":["/**\n * A policy is a tree that gives a {@link Decision.Decision} for a request.\n *\n * The leaves of the tree are lists of rules. A combining algorithm decides\n * which rule gives the answer when more than one rule applies:\n * - `denyOverrides` (the default): a deny rule wins over every allow rule.\n * - `permitOverrides`: an allow rule wins over every deny rule.\n * - `firstApplicable`: the first rule that applies wins.\n * - `lastApplicable`: the last rule that applies wins.\n *\n * The branches put policies together:\n * - {@link all}: every policy must allow, and one deny is enough to deny. Use\n * it when a user token may never do more than its user.\n * - {@link any}: one allow is enough.\n * - {@link firstOf}: the first policy that gives an answer wins. Use it for\n * grants first, then rules.\n * - {@link ceiling}: the deny rules of a second policy are a limit for\n * everyone. Its allow rules do nothing.\n *\n * A policy gives three kinds of answers:\n * - {@link decide}: the decision for one resource value.\n * - {@link residual}: a condition on resource values. A value passes when the\n * policy allows the request for that value. Use it to make a database\n * query, or to ask \"may this principal do this act on some resource of\n * this kind?\"\n * - {@link permittedFields}: the fields that the policy lets a request use.\n *\n * Each of them takes the policy first and the request second, as in\n * `Policy.decide(policy, request)`. The policy gives the domain, so\n * TypeScript checks the request against it.\n *\n * A policy without predicates can become JSON with {@link schema}.\n *\n * @stability stable\n * @since 0.3.0\n */\nimport type * as Context from \"effect/Context\"\nimport type * as Data from \"effect/Data\"\nimport type * as Effect from \"effect/Effect\"\nimport type * as Schema from \"effect/Schema\"\nimport type * as Condition from \"./Condition.ts\"\nimport * as Analysis from \"./internal/analysis.ts\"\nimport * as Decide from \"./internal/decide.ts\"\nimport * as Internal from \"./internal/policy.ts\"\nimport type * as PolicyNodes from \"./internal/policy-nodes.ts\"\nimport * as RuleData from \"./internal/rule.ts\"\nimport type * as Decision from \"./Decision.ts\"\nimport type * as Domain from \"./Domain.ts\"\nimport type * as Resource from \"./Resource.ts\"\n\n/**\n * @stability stable\n * @category models\n * @since 0.3.0\n */\nexport type Algorithm = PolicyNodes.Algorithm\n\n/**\n * A list of rules with a combining algorithm.\n *\n * @stability stable\n * @category models\n * @since 0.3.0\n */\nexport type RulesNode = PolicyNodes.Rules\n\n/**\n * @stability stable\n * @category models\n * @since 0.3.0\n */\nexport type AllNode = PolicyNodes.All\n\n/**\n * @stability stable\n * @category models\n * @since 0.3.0\n */\nexport type AnyNode = PolicyNodes.Any\n\n/**\n * @stability stable\n * @category models\n * @since 0.3.0\n */\nexport type FirstOfNode = PolicyNodes.FirstOf\n\n/**\n * @stability stable\n * @category models\n * @since 0.3.0\n */\nexport type CeilingNode = PolicyNodes.Ceiling\n\n/**\n * The nodes of a policy tree, without types. Each node is a class, and only\n * the functions of this module and the Schema make one.\n *\n * @stability stable\n * @category models\n * @since 0.3.0\n */\nexport type Node = PolicyNodes.Node\n\n/**\n * A policy for the domain `D`. `X` is `Condition.Opaque` when a rule of the\n * policy has a predicate, and `never` when the policy is plain data. A plain\n * policy can become JSON, and `decideSync` takes it.\n *\n * @stability stable\n * @category models\n * @since 0.3.0\n */\nexport type Policy<D extends Domain.Any, X = never> = Internal.PolicyData<D, X>\n\n/**\n * Any policy.\n *\n * @stability stable\n * @category models\n * @since 0.3.0\n */\nexport type Any = Policy<any, any>\n\n/**\n * The domain of a policy.\n *\n * @stability stable\n * @category utility types\n * @since 0.3.0\n */\nexport type DomainOf<P> = P extends Policy<infer D, any> ? D : never\n\n/**\n * The mark of a policy or a rule: `Condition.Opaque` when it has a predicate.\n *\n * @stability stable\n * @category utility types\n * @since 0.3.0\n */\nexport type OpaqueOf<P> = P extends { readonly \"~Opaque\": () => infer X }\n\t? X\n\t: never\n\n/**\n * One act on one resource value, for each kind of the domain. The action must\n * be an action of the kind, and the field must be a field of the kind.\n *\n * @stability stable\n * @category models\n * @since 0.3.0\n */\nexport type Target<D> = {\n\t[K in Domain.Kind<D>]: {\n\t\treadonly action: Domain.ActionOf<D, K>\n\t\treadonly resource: Domain.ValueOf<D, K>\n\t\treadonly field?: Condition.FieldSelector<Domain.DataOf<D, K>>\n\t\t/** A target has a resource value or a kind, never both. */\n\t\treadonly kind?: never\n\t}\n}[Domain.Kind<D>]\n\n/**\n * A {@link Target} with no field. Each kind keeps its own actions.\n *\n * @stability stable\n * @category models\n * @since 0.3.0\n */\nexport type FieldlessTarget<D> = {\n\t[K in Domain.Kind<D>]: {\n\t\treadonly action: Domain.ActionOf<D, K>\n\t\treadonly resource: Domain.ValueOf<D, K>\n\t\treadonly kind?: never\n\t}\n}[Domain.Kind<D>]\n\n/**\n * A scope: a part of a kind, as a condition on its values. It gets a builder\n * with the types of the kind and of the principal, like the condition of a\n * rule. A scope is plain data, so it can not have a predicate.\n *\n * @stability stable\n * @category models\n * @since 1.0.0\n */\nexport type Scope<D, K extends string> = (\n\tbuilder: Condition.Builder<\n\t\tDomain.DataOf<D, K>,\n\t\tDomain.ValueOf<D, K>,\n\t\tDomain.Principal<D>\n\t>,\n) => Condition.Condition<\n\tCondition.Reads<Domain.ValueOf<D, K>, Domain.Principal<D>>\n>\n\n/**\n * One act on one kind of resource, with no value. With `within`, the target is\n * only the values of the kind in the scope. Without it, the target is every\n * value of the kind.\n *\n * @stability stable\n * @category models\n * @since 0.3.0\n */\nexport type KindTarget<D> = {\n\t[K in Domain.Kind<D>]: {\n\t\treadonly action: Domain.ActionOf<D, K>\n\t\treadonly kind: K\n\t\treadonly field?: Condition.FieldSelector<Domain.DataOf<D, K>>\n\t\t/** The part of the kind. See {@link Scope}. */\n\t\treadonly within?: Scope<D, K>\n\t\t/** A target has a resource value or a kind, never both. */\n\t\treadonly resource?: never\n\t}\n}[Domain.Kind<D>]\n\n/**\n * A {@link Target} with a field of any length. A function that takes a\n * target checks its field with {@link CheckField}, so a field of up to about\n * 1000 parts is fine, and a field that is not a field of the kind is a type\n * error.\n *\n * @stability stable\n * @category models\n * @since 2.1.0\n */\nexport type DeepTarget<D> = {\n\t[K in Domain.Kind<D>]: {\n\t\treadonly action: Domain.ActionOf<D, K>\n\t\treadonly resource: Domain.ValueOf<D, K>\n\t\treadonly field?: string\n\t\treadonly kind?: never\n\t}\n}[Domain.Kind<D>]\n\n/**\n * A {@link KindTarget} with a field of any length. See {@link DeepTarget}.\n *\n * @stability stable\n * @category models\n * @since 2.1.0\n */\nexport type DeepKindTarget<D> = {\n\t[K in Domain.Kind<D>]: {\n\t\treadonly action: Domain.ActionOf<D, K>\n\t\treadonly kind: K\n\t\treadonly field?: string\n\t\treadonly within?: Scope<D, K>\n\t\treadonly resource?: never\n\t}\n}[Domain.Kind<D>]\n\n/** The kind of a target: its `kind`, or the `_tag` of its resource value. */\ntype KindOfTarget<T> = T extends { readonly kind: infer K extends string }\n\t? K\n\t: T extends { readonly resource: { readonly _tag: infer K extends string } }\n\t\t? K\n\t\t: never\n\n/**\n * Checks the field of a target or a request: a field of its kind that a\n * request may name, of any length up to about 1000 parts. See\n * `Condition.SelectPath`. A target with no field needs no check.\n *\n * @stability stable\n * @category utility types\n * @since 2.1.0\n */\nexport type CheckField<D, T> = T extends {\n\treadonly field: infer F extends string\n}\n\t? {\n\t\t\treadonly field: Condition.SelectPath<Domain.DataOf<D, KindOfTarget<T>>, F>\n\t\t}\n\t: unknown\n\n/**\n * A request: a principal, an action, and a resource value.\n *\n * @stability stable\n * @category models\n * @since 0.3.0\n */\nexport type Request<D> = Target<D> & { readonly principal: Domain.Principal<D> }\n\n/**\n * A {@link Request} with a field of any length. See {@link DeepTarget}.\n *\n * @stability stable\n * @category models\n * @since 2.1.0\n */\nexport type DeepRequest<D> = DeepTarget<D> & {\n\treadonly principal: Domain.Principal<D>\n}\n\n/**\n * A {@link Request} with no field.\n *\n * @stability stable\n * @category models\n * @since 0.3.0\n */\nexport type FieldlessRequest<D> = FieldlessTarget<D> & {\n\treadonly principal: Domain.Principal<D>\n}\n\n/**\n * A request about a kind of resource, with no value.\n *\n * @stability stable\n * @category models\n * @since 0.3.0\n */\nexport type KindRequest<D> = KindTarget<D> & {\n\treadonly principal: Domain.Principal<D>\n}\n\n/**\n * A {@link KindRequest} with a field of any length. See {@link DeepTarget}.\n *\n * @stability stable\n * @category models\n * @since 2.1.0\n */\nexport type DeepKindRequest<D> = DeepKindTarget<D> & {\n\treadonly principal: Domain.Principal<D>\n}\n\n/**\n * A request with no types.\n *\n * @stability stable\n * @category models\n * @since 0.3.0\n */\nexport interface AnyRequest {\n\treadonly principal: object\n\treadonly action: string\n\treadonly resource: object\n\treadonly field?: string | undefined\n}\n\n/**\n * One rule that a decision tested. `path` gives the place of the rule in the\n * policy tree: the index of each child, then the index of the rule.\n *\n * @stability stable\n * @category models\n * @since 0.3.0\n */\nexport interface Step {\n\treadonly path: ReadonlyArray<number>\n\treadonly rule: AnyRule\n\treadonly applies: boolean\n}\n\n/**\n * A decision, and the rules that were tested to make it, in order. Keep it in\n * an audit log.\n *\n * @stability stable\n * @category models\n * @since 0.3.0\n */\nexport interface Explanation {\n\treadonly decision: Decision.Decision\n\treadonly steps: ReadonlyArray<Step>\n}\n\n// Rules\n\n/**\n * A rule for the domain `D`. `I` is what the tests of its condition read, and\n * `X` is its mark: see `Condition.Condition`. A predicate reads the values and\n * the principal of its domain, so a rule may go only into a policy of its own\n * domain. A rule from {@link rule} has the domain `any`.\n *\n * @stability stable\n * @category models\n * @since 0.3.0\n */\nexport type Rule<D = any, I = unknown, X = never> = RuleData.Rule<D, I, X>\n\n/**\n * The data of a rule.\n *\n * @stability stable\n * @category models\n * @since 1.0.0\n */\nexport interface RuleShape<I = unknown, X = never> {\n\treadonly _tag: \"Allow\" | \"Deny\"\n\treadonly actions: ReadonlyArray<string>\n\treadonly resources: ReadonlyArray<string>\n\treadonly when?: Condition.Condition<I, X>\n\treadonly fields?: ReadonlyArray<string>\n\treadonly reason?: string\n\treadonly id?: string\n\treadonly meta?: unknown\n\t/**\n\t * The domain of the builder that made the rule. Two domains may have the\n\t * same types but other paths, so the types can not tell them apart. It is\n\t * not in the JSON form of a rule.\n\t */\n\treadonly domain?: Domain.Any\n}\n\n/**\n * Any rule.\n *\n * @stability stable\n * @category models\n * @since 0.3.0\n */\n// Every rule fits `never` as what it reads, and a condition taken from such a\n// rule fits no rule that reads a value or a principal.\nexport type AnyRule = Rule<any, never, any>\n\n/**\n * A resource in a rule: a kind name, a resource of the domain, or `\"*\"`.\n *\n * @stability stable\n * @category models\n * @since 0.3.0\n */\nexport type RuleTarget<D> = Domain.Kind<D> | Domain.Resources<D> | \"*\"\n\n/**\n * The kind name of a target.\n *\n * @stability stable\n * @category utility types\n * @since 0.3.0\n */\nexport type KindOf<T> = T extends string ? T : Resource.Kind<T>\n\n/**\n * The actions that a rule may use for the kinds `K`.\n *\n * @stability stable\n * @category utility types\n * @since 0.3.0\n */\nexport type ActionFor<D, K> =\n\t| \"*\"\n\t| (\"*\" extends K\n\t\t\t? Domain.AnyAction<D>\n\t\t\t: Domain.ActionOf<D, Extract<K, string>>)\n\n/**\n * The targets in the resource argument of a rule: the one target, or the\n * targets of a list.\n *\n * @stability stable\n * @category utility types\n * @since 1.0.0\n */\nexport type TargetOf<Res> = Res extends ReadonlyArray<infer X> ? X : Res\n\n/**\n * The actions that a rule may use for its resource argument. A list of\n * resources may use an action of any of them. One target with a union type\n * may use only an action of every kind in the union, because its value may be\n * any one of them. When the argument may be a list or one target, the rule\n * may use only an action that each of these shapes allows.\n *\n * @stability stable\n * @category utility types\n * @since 1.0.0\n */\nexport type ActionForTargets<D, Res> = (\n\tRes extends ReadonlyArray<infer X>\n\t\t? (action: ActionFor<D, KindOf<X>>) => void\n\t\t: (action: ActionForTarget<D, Res>) => void\n) extends (action: infer A) => void\n\t? A\n\t: never\n\n/** The actions for one target that is not a list. */\ntype ActionForTarget<D, Res> =\n\t| \"*\"\n\t| (\"*\" extends KindOf<Res>\n\t\t\t? Domain.AnyAction<D>\n\t\t\t: Domain.CommonAction<D, Extract<KindOf<Res>, string>>)\n\n/**\n * The data type of the kinds `K`. For `\"*\"` it has no fields.\n *\n * @stability stable\n * @category utility types\n * @since 0.3.0\n */\nexport type DataFor<D, K> = \"*\" extends K\n\t? {}\n\t: Domain.DataOf<D, Extract<K, string>>\n\n/**\n * The value type of the kinds `K`.\n *\n * @stability stable\n * @category utility types\n * @since 0.3.0\n */\nexport type ValueFor<D, K> = \"*\" extends K\n\t? Domain.AnyValue<D>\n\t: Domain.ValueOf<D, Extract<K, string>>\n\n/**\n * A field name or a field pattern of the kinds `K`.\n *\n * @stability stable\n * @category models\n * @since 0.3.0\n */\nexport type FieldFor<D, K> =\n\tCondition.FieldName<DataFor<D, K>> | `${string}*${string}`\n\n/**\n * A condition that a rule for the kinds `K` may have. Its predicates may read\n * only the values of these kinds and the principal of the domain.\n *\n * @stability stable\n * @category models\n * @since 1.0.0\n */\nexport type ConditionFor<D, K> = Condition.Condition<\n\tCondition.Reads<ValueFor<D, K>, Domain.Principal<D>>,\n\tany\n>\n\n/**\n * Checks each field of a rule: a field pattern with a star, or a name of the\n * kinds `K`, of any length up to about 1000 parts. A name that is not a field\n * gives the names that may come next, so the type error names them. See\n * `Condition.NamePath`.\n *\n * @stability stable\n * @category utility types\n * @since 2.1.0\n */\nexport type FieldsFor<D, K, Fields extends ReadonlyArray<string>> = {\n\treadonly [I in keyof Fields]: Fields[I] extends `${string}*${string}`\n\t\t? Fields[I]\n\t\t: Condition.NamePath<DataFor<D, K>, Fields[I] & string>\n}\n\n/**\n * The options of a rule. `Fields` is the list of the fields of the rule.\n *\n * @stability stable\n * @category models\n * @since 0.3.0\n */\nexport interface RuleOptions<\n\tD,\n\tK,\n\tC extends Condition.Any,\n\tFields extends ReadonlyArray<string> = ReadonlyArray<FieldFor<D, K>>,\n> {\n\t/** The condition. It gets a builder with the types of the resource. */\n\treadonly when?: (\n\t\tbuilder: Condition.Builder<\n\t\t\tDataFor<D, K>,\n\t\t\tValueFor<D, K>,\n\t\t\tDomain.Principal<D>\n\t\t>,\n\t) => C\n\treadonly fields?: Fields & FieldsFor<D, K, Fields>\n\treadonly reason?: string\n\treadonly id?: string\n\treadonly meta?: Domain.Meta<D>\n}\n\ntype SingleOrArray<A> = A | ReadonlyArray<A>\n\n/**\n * Makes rules with the types of a domain.\n *\n * @stability stable\n * @category models\n * @since 0.3.0\n */\nexport interface Builder<D> {\n\treadonly allow: <\n\t\tconst Res extends SingleOrArray<RuleTarget<D>>,\n\t\tconst A extends ActionForTargets<D, Res>,\n\t\tconst C extends ConditionFor<D, KindOf<TargetOf<Res>>> =\n\t\t\tCondition.Condition,\n\t\tconst Fields extends ReadonlyArray<string> = readonly [],\n\t>(\n\t\taction: SingleOrArray<A>,\n\t\tresource: Res,\n\t\toptions?: RuleOptions<D, KindOf<TargetOf<Res>>, C, Fields>,\n\t) => Rule<D, Condition.ReadsOf<C>, Condition.OpaqueOf<C>>\n\treadonly deny: <\n\t\tconst Res extends SingleOrArray<RuleTarget<D>>,\n\t\tconst A extends ActionForTargets<D, Res>,\n\t\tconst C extends ConditionFor<D, KindOf<TargetOf<Res>>> =\n\t\t\tCondition.Condition,\n\t\tconst Fields extends ReadonlyArray<string> = readonly [],\n\t>(\n\t\taction: SingleOrArray<A>,\n\t\tresource: Res,\n\t\toptions?: RuleOptions<D, KindOf<TargetOf<Res>>, C, Fields>,\n\t) => Rule<D, Condition.ReadsOf<C>, Condition.OpaqueOf<C>>\n}\n\n/**\n * Makes rules with the types of a domain. `Policy.define` gives one to its\n * generator. Use this one to make rules outside of a generator.\n *\n * @example\n * ```ts\n * const _ = Policy.builder(App)\n * const rule = _.allow(\"read\", Document, {\n * when: ($) => $.eq(\"ownerId\", $.principal(\"id\")),\n * })\n * ```\n *\n * @stability stable\n * @category constructors\n * @since 2.0.0\n */\nexport const builder = <D extends Domain.Any>(domain: D): Builder<D> =>\n\tRuleData.builder(domain) as never\n\n/**\n * Makes a rule from its data, with no types. It does not check the data\n * against a domain: a policy does that when the rule goes into it. Use\n * {@link builder} for rules with types, or {@link schema} for rules from JSON.\n *\n * The condition may not have a predicate. A policy checks the fields of a\n * condition against the resources of its rule, but it can not check what\n * the code of a predicate reads. A rule from the builder keeps that in its\n * types; this one could give a predicate of one kind to another kind.\n *\n * @stability stable\n * @category constructors\n * @since 2.0.0\n */\nexport const rule: <I = unknown>(\n\tdata: RuleShape<I, never>,\n\t// The types come from the data only, not from where the rule goes.\n) => Rule<any, NoInfer<I>> = RuleData.makePlain as never\n\n/**\n * The options of a list of rules.\n *\n * @stability stable\n * @category models\n * @since 0.3.0\n */\nexport interface Options {\n\t/** The default is `denyOverrides`. */\n\treadonly algorithm?: Algorithm\n}\n\n/**\n * The error when a rule does not fit its domain: it names a kind, an action,\n * a field or a field of the principal that the domain does not have, or it\n * tests a field with a value that the field can not hold. It has one issue\n * for each problem, with its path, and a suggestion when a name has a mistake.\n *\n * @stability stable\n * @category errors\n * @since 2.0.0\n */\nexport const PolicyError: typeof Internal.PolicyError = Internal.PolicyError\n\n/**\n * @stability stable\n * @category errors\n * @since 2.0.0\n */\nexport type PolicyError = Internal.PolicyError\n\n/** The mark of a union of rules: `Condition.Opaque` when one has a predicate. */\ntype RulesOpaque<Rules> = Rules extends { readonly \"~Opaque\": () => infer X }\n\t? X\n\t: never\n\n/**\n * Makes a policy from a generator function that gives rules. It is pure and it\n * gives its result at once.\n *\n * @example\n * ```ts\n * const policy = Policy.define(App)(function* (_) {\n * yield* _.allow(\"read\", Document)\n * yield* _.allow([\"update\", \"delete\"], Document, {\n * when: ($) => $.eq(\"ownerId\", $.principal(\"id\")),\n * })\n * })\n * ```\n *\n * @stability stable\n * @category constructors\n * @since 0.3.0\n */\nexport const define: <D extends Domain.Any>(\n\tdomain: D,\n\toptions?: Options,\n) => <Rules extends Rule<D, never, any>, A>(\n\tbody: (builder: Builder<D>) => Generator<Rules, A, unknown>,\n) => Policy<D, RulesOpaque<Rules>> = Internal.define as never\n\ntype Combined<D extends Domain.Any, Ps extends ReadonlyArray<Any>> = Policy<\n\tD,\n\tOpaqueOf<Ps[number]>\n>\n\n/**\n * Every policy must allow, and one deny is enough to deny. With some policies\n * not applicable and none that deny, the result is not applicable.\n *\n * @stability stable\n * @category combinators\n * @since 0.3.0\n */\nexport const all: <const Ps extends readonly [Any, ...Array<Any>]>(\n\tpolicies: Ps,\n) => Combined<DomainOf<Ps[0]>, Ps> = Internal.combine(\"All\")\n\n/**\n * One allow is enough. Without an allow, one deny gives a deny.\n *\n * @stability stable\n * @category combinators\n * @since 0.3.0\n */\nexport const any: <const Ps extends readonly [Any, ...Array<Any>]>(\n\tpolicies: Ps,\n) => Combined<DomainOf<Ps[0]>, Ps> = Internal.combine(\"Any\")\n\n/**\n * The first policy that allows or denies gives the answer.\n *\n * @stability stable\n * @category combinators\n * @since 0.3.0\n */\nexport const firstOf: <const Ps extends readonly [Any, ...Array<Any>]>(\n\tpolicies: Ps,\n) => Combined<DomainOf<Ps[0]>, Ps> = Internal.combine(\"FirstOf\")\n\n/**\n * Puts a limit on a policy. A deny from `cap` always wins. An allow from `cap`\n * does nothing.\n *\n * @stability stable\n * @category combinators\n * @since 0.3.0\n */\nexport const ceiling: {\n\t<D extends Domain.Any, X2>(\n\t\tcap: Policy<D, X2>,\n\t): <X>(policy: Policy<D, X>) => Policy<D, X | X2>\n\t<D extends Domain.Any, X, X2>(\n\t\tpolicy: Policy<D, X>,\n\t\tcap: Policy<D, X2>,\n\t): Policy<D, X | X2>\n} = Internal.ceiling as never\n\n/**\n * Gives the services in `context` to every predicate of a policy, so the\n * policy needs no services when it runs.\n *\n * @stability stable\n * @category services\n * @since 1.0.0\n */\nexport const provideContext: {\n\t<R>(\n\t\tcontext: Context.Context<R>,\n\t): <D extends Domain.Any, X>(\n\t\tpolicy: Policy<D, X> &\n\t\t\t([Condition.ServicesOf<X>] extends [R] ? unknown : never),\n\t) => Policy<D, Condition.Provided<X>>\n\t<D extends Domain.Any, X>(\n\t\tpolicy: Policy<D, X>,\n\t\tcontext: Context.Context<Condition.ServicesOf<X>>,\n\t): Policy<D, Condition.Provided<X>>\n} = Internal.provideContext as never\n\n// Decisions\n\n/**\n * Gives the decision of a policy for a request.\n *\n * @stability stable\n * @category evaluation\n * @since 0.3.0\n */\nexport const decide: <\n\tD extends Domain.Any,\n\tX,\n\tconst Req extends DeepRequest<NoInfer<D>>,\n>(\n\tpolicy: Policy<D, X>,\n\trequest: Req & CheckField<NoInfer<D>, Req>,\n) => Effect.Effect<\n\tDecision.Decision,\n\tCondition.ErrorOf<X>,\n\tCondition.ServicesOf<X>\n> = Decide.decideAny as never\n\n/**\n * Gives the decision of a policy with no predicates, at once.\n *\n * @stability stable\n * @category evaluation\n * @since 0.3.0\n */\nexport const decideSync: <\n\tD extends Domain.Any,\n\tconst Req extends DeepRequest<NoInfer<D>>,\n>(\n\tpolicy: Policy<D>,\n\trequest: Req & CheckField<NoInfer<D>, Req>,\n) => Decision.Decision = Decide.decideSync\n\n/**\n * Gives the decision of a policy and the rules that were tested to make it.\n *\n * One node may be a child of many nodes. Its rules are tested, and are in the\n * steps, each time that the decision comes to it.\n *\n * @stability stable\n * @category evaluation\n * @since 0.3.0\n */\nexport const explain: <\n\tD extends Domain.Any,\n\tX,\n\tconst Req extends DeepRequest<NoInfer<D>>,\n>(\n\tpolicy: Policy<D, X>,\n\trequest: Req & CheckField<NoInfer<D>, Req>,\n) => Effect.Effect<Explanation, Condition.ErrorOf<X>, Condition.ServicesOf<X>> =\n\tDecide.explain as never\n\n/**\n * Gives the fields of a resource value that the policy lets a request use. It\n * tests each field that a list of fields shows: each scalar, record or array\n * of the resource Schema, with dots for nested fields.\n *\n * A field that holds more than one value, such as a record, is left out when a\n * deny rule names a part of it. The list can not name that part, and the whole\n * field would show it.\n *\n * @stability stable\n * @category evaluation\n * @since 0.3.0\n */\nexport const permittedFields: <D extends Domain.Any, X>(\n\tpolicy: Policy<D, X>,\n\trequest: FieldlessRequest<NoInfer<D>>,\n) => Effect.Effect<\n\tReadonlyArray<string>,\n\tCondition.ErrorOf<X>,\n\tCondition.ServicesOf<X>\n> = Decide.permittedFields as never\n\n// Residual conditions\n\n/**\n * What a residual condition reads: a value of the kind `K`. The principal is\n * already in it. A residual is for one kind, so when `K` is a union of kinds,\n * the value must be a value of each of them. No value is, so such a residual\n * can not be evaluated on a value of the wrong kind.\n *\n * @stability stable\n * @category utility types\n * @since 1.0.0\n */\nexport type ResidualReads<D, K> = Condition.Reads<\n\t(\n\t\tK extends unknown ? (value: Domain.ValueOf<D, K & string>) => void : never\n\t) extends (value: infer V) => void\n\t\t? V\n\t\t: never,\n\tunknown\n>\n\n/**\n * Gives the condition on resource values for which the policy allows a\n * request. It has no principal fields and no `Under` nodes, so you can give it\n * to `Condition.fold`. It is made smaller, so it is often `True` or `False`\n * when the answer does not depend on the value, but it does no search: a\n * condition that is true for every value may stay a test. To know if the\n * policy allows every value, or some value, use {@link coverage}.\n *\n * With a scope (`within`), the condition is the scope and the residual\n * together, so it is false for every value outside the scope. Give the policy\n * first to use a scope. The condition comes from its parts, with no search. To\n * know if it allows all of the scope or none of it, use {@link coverage}.\n *\n * When a rule that the request may use has a predicate, the condition is one\n * predicate with the name `\"policy\"`. It runs the whole policy for each value,\n * so its answers and its failures are the same as those of {@link decide}. It\n * takes the principal with it. Give it resource values with their `_tag`. It\n * is false for a value of another kind.\n *\n * A policy tree may use the condition of a child two times, so a small policy\n * may give a very large condition. When the condition would have more than\n * 10000 nodes, this function throws an error. In an `Authorizer`, the error\n * is a defect, so the check fails.\n *\n * @stability stable\n * @category evaluation\n * @since 0.3.0\n */\nexport const residual: <\n\tD extends Domain.Any,\n\tX,\n\tconst Req extends DeepKindRequest<NoInfer<D>>,\n>(\n\tpolicy: Policy<D, X>,\n\trequest: Req & CheckField<NoInfer<D>, Req>,\n) => Condition.Condition<ResidualReads<D, Req[\"kind\"]>, X> =\n\tAnalysis.residual as never\n\n/**\n * How much of a scope a policy allows. In each case, `condition` is the exact\n * condition on the values in the scope that the policy allows: give it to\n * `Condition.fold` for a list query.\n * - `All`: the policy allows every value in the scope. `condition` is the\n * scope (`True` with no scope).\n * - `None`: the policy allows no value in the scope. `condition` is `False`.\n * An empty scope is `None` too, so a check on a scope that has a mistake,\n * such as a principal field with no value, never passes.\n * - `Some`: the policy allows some values in the scope and not others.\n * `allowed` is an example of a value that it allows, and `refused` of a\n * value in the scope that it does not allow.\n * - `Unknown`: the answer needs a predicate, or the search went past its\n * budget.\n *\n * @stability stable\n * @category models\n * @since 1.0.0\n */\nexport type Coverage<I = unknown, X = never> = Data.TaggedEnum<{\n\tAll: { readonly condition: Condition.Condition<I, X> }\n\tNone: { readonly condition: Condition.Condition<I, X> }\n\tSome: {\n\t\treadonly condition: Condition.Condition<I, X>\n\t\treadonly allowed: Condition.Example\n\t\treadonly refused: Condition.Example\n\t}\n\tUnknown: {\n\t\treadonly condition: Condition.Condition<I, X>\n\t\treadonly reason: \"predicate\" | \"budget\"\n\t}\n}>\n\n/**\n * Tells how much of a scope the policy allows: all of it, none of it, or some\n * of it. See {@link Coverage}. With no scope (`within`), the scope is every\n * value of the kind.\n *\n * The answer comes from {@link Condition.satisfiable} with the shape of the\n * Schema of the resource. `All` and `None` hold for every value of the\n * Schema. `Some` means that, by the shape of the Schema, a value may get\n * either answer: a check such as a minimum length may still stop one of the\n * examples. `Unknown` comes only from a predicate or from the budget of the\n * search.\n *\n * `Some` does not mean that such a value is in your database. To know that,\n * run `condition` as a query.\n *\n * @stability stable\n * @category evaluation\n * @since 1.0.0\n */\nexport const coverage: <\n\tD extends Domain.Any,\n\tX,\n\tconst Req extends DeepKindRequest<NoInfer<D>>,\n>(\n\tpolicy: Policy<D, X>,\n\trequest: Req & CheckField<NoInfer<D>, Req>,\n) => Coverage<ResidualReads<D, Req[\"kind\"]>, X> = Analysis.coverageAny as never\n\n// Schema\n\n/**\n * The JSON form of a rule.\n *\n * @stability stable\n * @category models\n * @since 0.3.0\n */\nexport interface EncodedRule {\n\treadonly _tag: \"Allow\" | \"Deny\"\n\treadonly actions: ReadonlyArray<string>\n\treadonly resources: ReadonlyArray<string>\n\treadonly when?: Condition.Encoded\n\treadonly fields?: ReadonlyArray<string>\n\treadonly reason?: string\n\treadonly id?: string\n\treadonly meta?: unknown\n}\n\n/**\n * The JSON form of a policy.\n *\n * @stability stable\n * @category models\n * @since 0.3.0\n */\nexport type EncodedNode =\n\t| {\n\t\t\treadonly _tag: \"Rules\"\n\t\t\treadonly algorithm: Algorithm\n\t\t\treadonly rules: ReadonlyArray<EncodedRule>\n\t }\n\t| {\n\t\t\treadonly _tag: \"All\" | \"Any\" | \"FirstOf\"\n\t\t\treadonly policies: ReadonlyArray<EncodedNode>\n\t }\n\t| {\n\t\t\treadonly _tag: \"Ceiling\"\n\t\t\treadonly cap: EncodedNode\n\t\t\treadonly policy: EncodedNode\n\t }\n\n/**\n * The Schema of the policies of a domain with no predicates. Decoding checks\n * every kind, action, field and principal field against the domain, and the\n * `meta` of each rule against the `meta` Schema of the domain.\n *\n * The decoder takes at most 256 levels of policies and 500 levels of\n * conditions, and at most 1000000 objects and array items, with a shared\n * object counted for each place. It reads only the own data fields of the\n * input.\n *\n * @stability stable\n * @category schemas\n * @since 0.3.0\n */\nexport const schema: <D extends Domain.Any>(\n\tdomain: D,\n) => Schema.Codec<Policy<D>, EncodedNode> = Internal.schema as never\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAinBA,MAAa,WAAiC,WAC7CA,UAAiB,MAAM;;;;;;;;;;;;;;;AAgBxB,MAAa,OAGgBC;;;;;;;;;;;AAwB7B,MAAa,cAA2CC;;;;;;;;;;;;;;;;;;;AAgCxD,MAAa,SAKwBC;;;;;;;;;AAerC,MAAa,MAEwBC,QAAiB,KAAK;;;;;;;;AAS3D,MAAa,MAEwBA,QAAiB,KAAK;;;;;;;;AAS3D,MAAa,UAEwBA,QAAiB,SAAS;;;;;;;;;AAU/D,MAAa,UAQTC;;;;;;;;;AAUJ,MAAa,iBAWTC;;;;;;;;AAWJ,MAAa,SAWTC;;;;;;;;AASJ,MAAa,aAMYC;;;;;;;;;;;AAYzB,MAAa,UAQZC;;;;;;;;;;;;;;AAeD,MAAa,kBAOTC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAmDJ,MAAa,WAQZC;;;;;;;;;;;;;;;;;;;;AAsDD,MAAa,WAOqCC;;;;;;;;;;;;;;;AA2DlD,MAAa,SAE+BC"}
1
+ {"version":3,"file":"Policy.js","names":["RuleData.builder","RuleData.makePlain","Internal.PolicyError","Internal.define","Internal.combine","Internal.ceiling","Internal.provideContext","Decide.decideAny","Decide.decideSync","Decide.explain","Decide.permittedFields","Analysis.residual","Analysis.coverageAny","Internal.schema"],"sources":["../src/Policy.ts"],"sourcesContent":["/**\n * A policy is a tree that gives a {@link Decision.Decision} for a request.\n *\n * The leaves of the tree are lists of rules. A combining algorithm decides\n * which rule gives the answer when more than one rule applies:\n * - `denyOverrides` (the default): a deny rule wins over every allow rule.\n * - `permitOverrides`: an allow rule wins over every deny rule.\n * - `firstApplicable`: the first rule that applies wins.\n * - `lastApplicable`: the last rule that applies wins.\n *\n * The branches put policies together:\n * - {@link all}: every policy must allow, and one deny is enough to deny. Use\n * it when a user token may never do more than its user.\n * - {@link any}: one allow is enough.\n * - {@link firstOf}: the first policy that gives an answer wins. Use it for\n * grants first, then rules.\n * - {@link ceiling}: the deny rules of a second policy are a limit for\n * everyone. Its allow rules do nothing.\n *\n * A policy gives three kinds of answers:\n * - {@link decide}: the decision for one resource value.\n * - {@link residual}: a condition on resource values. A value passes when the\n * policy allows the request for that value. Use it to make a database\n * query, or to ask \"may this principal do this act on some resource of\n * this kind?\"\n * - {@link permittedFields}: the fields that the policy lets a request use.\n *\n * Each of them takes the policy first and the request second, as in\n * `Policy.decide(policy, request)`. The policy gives the domain, so\n * TypeScript checks the request against it.\n *\n * A policy without predicates can become JSON with {@link schema}.\n *\n * @stability stable\n * @since 0.3.0\n */\nimport type * as Context from \"effect/Context\"\nimport type * as Data from \"effect/Data\"\nimport type * as Effect from \"effect/Effect\"\nimport type * as Schema from \"effect/Schema\"\nimport type * as Condition from \"./Condition.ts\"\nimport * as Analysis from \"./internal/analysis.ts\"\nimport * as Decide from \"./internal/decide.ts\"\nimport * as Internal from \"./internal/policy.ts\"\nimport type * as PolicyNodes from \"./internal/policy-nodes.ts\"\nimport * as RuleData from \"./internal/rule.ts\"\nimport type * as Decision from \"./Decision.ts\"\nimport type * as Domain from \"./Domain.ts\"\nimport type * as Resource from \"./Resource.ts\"\n\n/**\n * @stability stable\n * @category models\n * @since 0.3.0\n */\nexport type Algorithm = PolicyNodes.Algorithm\n\n/**\n * A list of rules with a combining algorithm.\n *\n * @stability stable\n * @category models\n * @since 0.3.0\n */\nexport type RulesNode = PolicyNodes.Rules\n\n/**\n * @stability stable\n * @category models\n * @since 0.3.0\n */\nexport type AllNode = PolicyNodes.All\n\n/**\n * @stability stable\n * @category models\n * @since 0.3.0\n */\nexport type AnyNode = PolicyNodes.Any\n\n/**\n * @stability stable\n * @category models\n * @since 0.3.0\n */\nexport type FirstOfNode = PolicyNodes.FirstOf\n\n/**\n * @stability stable\n * @category models\n * @since 0.3.0\n */\nexport type CeilingNode = PolicyNodes.Ceiling\n\n/**\n * The nodes of a policy tree, without types. Each node is a class, and only\n * the functions of this module and the Schema make one.\n *\n * @stability stable\n * @category models\n * @since 0.3.0\n */\nexport type Node = PolicyNodes.Node\n\n/**\n * A policy for the domain `D`. `X` is `Condition.Opaque` when a rule of the\n * policy has a predicate, and `never` when the policy is plain data. A plain\n * policy can become JSON, and `decideSync` takes it.\n *\n * @stability stable\n * @category models\n * @since 0.3.0\n */\nexport type Policy<D extends Domain.Any, X = never> = Internal.PolicyData<D, X>\n\n/**\n * Any policy.\n *\n * @stability stable\n * @category models\n * @since 0.3.0\n */\nexport type Any = Policy<any, any>\n\n/**\n * The domain of a policy.\n *\n * @stability stable\n * @category utility types\n * @since 0.3.0\n */\nexport type DomainOf<P> = P extends Policy<infer D, any> ? D : never\n\n/**\n * The mark of a policy or a rule: `Condition.Opaque` when it has a predicate.\n *\n * @stability stable\n * @category utility types\n * @since 0.3.0\n */\nexport type OpaqueOf<P> = P extends { readonly \"~Opaque\": () => infer X }\n\t? X\n\t: never\n\n/**\n * One act on one resource value, for each kind of the domain. The action must\n * be an action of the kind, and the field must be a field of the kind.\n *\n * @stability stable\n * @category models\n * @since 0.3.0\n */\nexport type Target<D> = {\n\t[K in Domain.Kind<D>]: {\n\t\treadonly action: Domain.ActionOf<D, K>\n\t\treadonly resource: Domain.ValueOf<D, K>\n\t\treadonly field?: Condition.FieldSelector<Domain.DataOf<D, K>>\n\t\t/** A target has a resource value or a kind, never both. */\n\t\treadonly kind?: never\n\t}\n}[Domain.Kind<D>]\n\n/**\n * A {@link Target} with no field. Each kind keeps its own actions.\n *\n * @stability stable\n * @category models\n * @since 0.3.0\n */\nexport type FieldlessTarget<D> = {\n\t[K in Domain.Kind<D>]: {\n\t\treadonly action: Domain.ActionOf<D, K>\n\t\treadonly resource: Domain.ValueOf<D, K>\n\t\treadonly kind?: never\n\t}\n}[Domain.Kind<D>]\n\n/**\n * A scope: a part of a kind, as a condition on its values. It gets a builder\n * with the types of the kind and of the principal, like the condition of a\n * rule. A scope is plain data, so it can not have a predicate.\n *\n * @stability stable\n * @category models\n * @since 1.0.0\n */\nexport type Scope<D, K extends string> = (\n\tbuilder: Condition.Builder<\n\t\tDomain.DataOf<D, K>,\n\t\tDomain.ValueOf<D, K>,\n\t\tDomain.Principal<D>\n\t>,\n) => Condition.Condition<\n\tCondition.Reads<Domain.ValueOf<D, K>, Domain.Principal<D>>\n>\n\n/**\n * One act on one kind of resource, with no value. With `within`, the target is\n * only the values of the kind in the scope. Without it, the target is every\n * value of the kind.\n *\n * @stability stable\n * @category models\n * @since 0.3.0\n */\nexport type KindTarget<D> = {\n\t[K in Domain.Kind<D>]: {\n\t\treadonly action: Domain.ActionOf<D, K>\n\t\treadonly kind: K\n\t\treadonly field?: Condition.FieldSelector<Domain.DataOf<D, K>>\n\t\t/** The part of the kind. See {@link Scope}. */\n\t\treadonly within?: Scope<D, K>\n\t\t/** A target has a resource value or a kind, never both. */\n\t\treadonly resource?: never\n\t}\n}[Domain.Kind<D>]\n\n/**\n * A {@link Target} with a field of any length. A function that takes a\n * target checks its field with {@link CheckField}, so a field of up to about\n * 1000 parts is fine, and a field that is not a field of the kind is a type\n * error.\n *\n * @stability stable\n * @category models\n * @since 2.1.0\n */\nexport type DeepTarget<D> = {\n\t[K in Domain.Kind<D>]: {\n\t\treadonly action: Domain.ActionOf<D, K>\n\t\treadonly resource: Domain.ValueOf<D, K>\n\t\treadonly field?: string\n\t\treadonly kind?: never\n\t}\n}[Domain.Kind<D>]\n\n/**\n * A {@link KindTarget} with a field of any length. See {@link DeepTarget}.\n *\n * @stability stable\n * @category models\n * @since 2.1.0\n */\nexport type DeepKindTarget<D> = {\n\t[K in Domain.Kind<D>]: {\n\t\treadonly action: Domain.ActionOf<D, K>\n\t\treadonly kind: K\n\t\treadonly field?: string\n\t\treadonly within?: Scope<D, K>\n\t\treadonly resource?: never\n\t}\n}[Domain.Kind<D>]\n\n/** The kind of a target: its `kind`, or the `_tag` of its resource value. */\ntype KindOfTarget<T> = T extends { readonly kind: infer K extends string }\n\t? K\n\t: T extends { readonly resource: { readonly _tag: infer K extends string } }\n\t\t? K\n\t\t: never\n\n/**\n * Checks the field of a target or a request: a field of its kind that a\n * request may name, of any length up to about 1000 parts. See\n * `Condition.SelectPath`. A target with no field needs no check.\n *\n * @stability stable\n * @category utility types\n * @since 2.1.0\n */\nexport type CheckField<D, T> = T extends { readonly field?: infer F }\n\t? [Exclude<F, undefined>] extends [never]\n\t\t? unknown\n\t\t: {\n\t\t\t\t// A field that the target may leave out is checked too, when it is\n\t\t\t\t// there.\n\t\t\t\treadonly field?: Condition.SelectPath<\n\t\t\t\t\tDomain.DataOf<D, KindOfTarget<T>>,\n\t\t\t\t\tExclude<F, undefined> & string\n\t\t\t\t>\n\t\t\t}\n\t: unknown\n\n/**\n * A request: a principal, an action, and a resource value.\n *\n * @stability stable\n * @category models\n * @since 0.3.0\n */\nexport type Request<D> = Target<D> & { readonly principal: Domain.Principal<D> }\n\n/**\n * A {@link Request} with a field of any length. See {@link DeepTarget}.\n *\n * @stability stable\n * @category models\n * @since 2.1.0\n */\nexport type DeepRequest<D> = DeepTarget<D> & {\n\treadonly principal: Domain.Principal<D>\n}\n\n/**\n * A {@link Request} with no field.\n *\n * @stability stable\n * @category models\n * @since 0.3.0\n */\nexport type FieldlessRequest<D> = FieldlessTarget<D> & {\n\treadonly principal: Domain.Principal<D>\n}\n\n/**\n * A request about a kind of resource, with no value.\n *\n * @stability stable\n * @category models\n * @since 0.3.0\n */\nexport type KindRequest<D> = KindTarget<D> & {\n\treadonly principal: Domain.Principal<D>\n}\n\n/**\n * A {@link KindRequest} with a field of any length. See {@link DeepTarget}.\n *\n * @stability stable\n * @category models\n * @since 2.1.0\n */\nexport type DeepKindRequest<D> = DeepKindTarget<D> & {\n\treadonly principal: Domain.Principal<D>\n}\n\n/**\n * A request with no types.\n *\n * @stability stable\n * @category models\n * @since 0.3.0\n */\nexport interface AnyRequest {\n\treadonly principal: object\n\treadonly action: string\n\treadonly resource: object\n\treadonly field?: string | undefined\n}\n\n/**\n * One rule that a decision tested. `path` gives the place of the rule in the\n * policy tree: the index of each child, then the index of the rule.\n *\n * @stability stable\n * @category models\n * @since 0.3.0\n */\nexport interface Step {\n\treadonly path: ReadonlyArray<number>\n\treadonly rule: AnyRule\n\treadonly applies: boolean\n}\n\n/**\n * A decision, and the rules that were tested to make it, in order. Keep it in\n * an audit log.\n *\n * @stability stable\n * @category models\n * @since 0.3.0\n */\nexport interface Explanation {\n\treadonly decision: Decision.Decision\n\treadonly steps: ReadonlyArray<Step>\n}\n\n// Rules\n\n/**\n * A rule for the domain `D`. `I` is what the tests of its condition read, and\n * `X` is its mark: see `Condition.Condition`. A predicate reads the values and\n * the principal of its domain, so a rule may go only into a policy of its own\n * domain. A rule from {@link rule} has the domain `any`.\n *\n * @stability stable\n * @category models\n * @since 0.3.0\n */\nexport type Rule<D = any, I = unknown, X = never> = RuleData.Rule<D, I, X>\n\n/**\n * The data of a rule.\n *\n * @stability stable\n * @category models\n * @since 1.0.0\n */\nexport interface RuleShape<I = unknown, X = never> {\n\treadonly _tag: \"Allow\" | \"Deny\"\n\treadonly actions: ReadonlyArray<string>\n\treadonly resources: ReadonlyArray<string>\n\treadonly when?: Condition.Condition<I, X>\n\treadonly fields?: ReadonlyArray<string>\n\treadonly reason?: string\n\treadonly id?: string\n\treadonly meta?: unknown\n\t/**\n\t * The domain of the builder that made the rule. Two domains may have the\n\t * same types but other paths, so the types can not tell them apart. It is\n\t * not in the JSON form of a rule.\n\t */\n\treadonly domain?: Domain.Any\n}\n\n/**\n * Any rule.\n *\n * @stability stable\n * @category models\n * @since 0.3.0\n */\n// Every rule fits `never` as what it reads, and a condition taken from such a\n// rule fits no rule that reads a value or a principal.\nexport type AnyRule = Rule<any, never, any>\n\n/**\n * A resource in a rule: a kind name, a resource of the domain, or `\"*\"`.\n *\n * @stability stable\n * @category models\n * @since 0.3.0\n */\nexport type RuleTarget<D> = Domain.Kind<D> | Domain.Resources<D> | \"*\"\n\n/**\n * The kind name of a target.\n *\n * @stability stable\n * @category utility types\n * @since 0.3.0\n */\nexport type KindOf<T> = T extends string ? T : Resource.Kind<T>\n\n/**\n * The actions that a rule may use for the kinds `K`.\n *\n * @stability stable\n * @category utility types\n * @since 0.3.0\n */\nexport type ActionFor<D, K> =\n\t| \"*\"\n\t| (\"*\" extends K\n\t\t\t? Domain.AnyAction<D>\n\t\t\t: Domain.ActionOf<D, Extract<K, string>>)\n\n/**\n * The targets in the resource argument of a rule: the one target, or the\n * targets of a list.\n *\n * @stability stable\n * @category utility types\n * @since 1.0.0\n */\nexport type TargetOf<Res> = Res extends ReadonlyArray<infer X> ? X : Res\n\n/**\n * The actions that a rule may use for its resource argument. A list of\n * resources may use an action of any of them. One target with a union type\n * may use only an action of every kind in the union, because its value may be\n * any one of them. When the argument may be a list or one target, the rule\n * may use only an action that each of these shapes allows.\n *\n * @stability stable\n * @category utility types\n * @since 1.0.0\n */\nexport type ActionForTargets<D, Res> = (\n\tRes extends ReadonlyArray<infer X>\n\t\t? (action: ActionFor<D, KindOf<X>>) => void\n\t\t: (action: ActionForTarget<D, Res>) => void\n) extends (action: infer A) => void\n\t? A\n\t: never\n\n/** The actions for one target that is not a list. */\ntype ActionForTarget<D, Res> =\n\t| \"*\"\n\t| (\"*\" extends KindOf<Res>\n\t\t\t? Domain.AnyAction<D>\n\t\t\t: Domain.CommonAction<D, Extract<KindOf<Res>, string>>)\n\n/**\n * The data type of the kinds `K`. For `\"*\"` it has no fields.\n *\n * @stability stable\n * @category utility types\n * @since 0.3.0\n */\nexport type DataFor<D, K> = \"*\" extends K\n\t? {}\n\t: Domain.DataOf<D, Extract<K, string>>\n\n/**\n * The value type of the kinds `K`.\n *\n * @stability stable\n * @category utility types\n * @since 0.3.0\n */\nexport type ValueFor<D, K> = \"*\" extends K\n\t? Domain.AnyValue<D>\n\t: Domain.ValueOf<D, Extract<K, string>>\n\n/**\n * A field name or a field pattern of the kinds `K`.\n *\n * @stability stable\n * @category models\n * @since 0.3.0\n */\nexport type FieldFor<D, K> =\n\tCondition.FieldName<DataFor<D, K>> | `${string}*${string}`\n\n/**\n * A condition that a rule for the kinds `K` may have. Its predicates may read\n * only the values of these kinds and the principal of the domain.\n *\n * @stability stable\n * @category models\n * @since 1.0.0\n */\nexport type ConditionFor<D, K> = Condition.Condition<\n\tCondition.Reads<ValueFor<D, K>, Domain.Principal<D>>,\n\tany\n>\n\n/**\n * Checks each field of a rule: a field pattern with a star, or a name of the\n * kinds `K`, of any length up to about 1000 parts. A name that is not a field\n * gives the names that may come next, so the type error names them. See\n * `Condition.NamePath`.\n *\n * @stability stable\n * @category utility types\n * @since 2.1.0\n */\nexport type FieldsFor<D, K, Fields extends ReadonlyArray<string>> = {\n\treadonly [I in keyof Fields]: Fields[I] extends `${string}*${string}`\n\t\t? Fields[I]\n\t\t: Condition.NamePath<DataFor<D, K>, Fields[I] & string>\n}\n\n/**\n * The options of a rule. `Fields` is the list of the fields of the rule.\n *\n * @stability stable\n * @category models\n * @since 0.3.0\n */\nexport interface RuleOptions<\n\tD,\n\tK,\n\tC extends Condition.Any,\n\tFields extends ReadonlyArray<string> = ReadonlyArray<FieldFor<D, K>>,\n> {\n\t/** The condition. It gets a builder with the types of the resource. */\n\treadonly when?: (\n\t\tbuilder: Condition.Builder<\n\t\t\tDataFor<D, K>,\n\t\t\tValueFor<D, K>,\n\t\t\tDomain.Principal<D>\n\t\t>,\n\t) => C\n\treadonly fields?: Fields & FieldsFor<D, K, Fields>\n\treadonly reason?: string\n\treadonly id?: string\n\treadonly meta?: Domain.Meta<D>\n}\n\ntype SingleOrArray<A> = A | ReadonlyArray<A>\n\n/**\n * Makes rules with the types of a domain.\n *\n * @stability stable\n * @category models\n * @since 0.3.0\n */\nexport interface Builder<D> {\n\treadonly allow: <\n\t\tconst Res extends SingleOrArray<RuleTarget<D>>,\n\t\tconst A extends ActionForTargets<D, Res>,\n\t\tconst C extends ConditionFor<D, KindOf<TargetOf<Res>>> =\n\t\t\tCondition.Condition,\n\t\tconst Fields extends ReadonlyArray<string> = readonly [],\n\t>(\n\t\taction: SingleOrArray<A>,\n\t\tresource: Res,\n\t\toptions?: RuleOptions<D, KindOf<TargetOf<Res>>, C, Fields>,\n\t) => Rule<D, Condition.ReadsOf<C>, Condition.OpaqueOf<C>>\n\treadonly deny: <\n\t\tconst Res extends SingleOrArray<RuleTarget<D>>,\n\t\tconst A extends ActionForTargets<D, Res>,\n\t\tconst C extends ConditionFor<D, KindOf<TargetOf<Res>>> =\n\t\t\tCondition.Condition,\n\t\tconst Fields extends ReadonlyArray<string> = readonly [],\n\t>(\n\t\taction: SingleOrArray<A>,\n\t\tresource: Res,\n\t\toptions?: RuleOptions<D, KindOf<TargetOf<Res>>, C, Fields>,\n\t) => Rule<D, Condition.ReadsOf<C>, Condition.OpaqueOf<C>>\n}\n\n/**\n * Makes rules with the types of a domain. `Policy.define` gives one to its\n * generator. Use this one to make rules outside of a generator.\n *\n * @example\n * ```ts\n * const _ = Policy.builder(App)\n * const rule = _.allow(\"read\", Document, {\n * when: ($) => $.eq(\"ownerId\", $.principal(\"id\")),\n * })\n * ```\n *\n * @stability stable\n * @category constructors\n * @since 2.0.0\n */\nexport const builder = <D extends Domain.Any>(domain: D): Builder<D> =>\n\tRuleData.builder(domain) as never\n\n/**\n * Makes a rule from its data, with no types. It does not check the data\n * against a domain: a policy does that when the rule goes into it. Use\n * {@link builder} for rules with types, or {@link schema} for rules from JSON.\n *\n * The condition may not have a predicate. A policy checks the fields of a\n * condition against the resources of its rule, but it can not check what\n * the code of a predicate reads. A rule from the builder keeps that in its\n * types; this one could give a predicate of one kind to another kind.\n *\n * @stability stable\n * @category constructors\n * @since 2.0.0\n */\nexport const rule: <I = unknown>(\n\tdata: RuleShape<I, never>,\n\t// The types come from the data only, not from where the rule goes.\n) => Rule<any, NoInfer<I>> = RuleData.makePlain as never\n\n/**\n * The options of a list of rules.\n *\n * @stability stable\n * @category models\n * @since 0.3.0\n */\nexport interface Options {\n\t/** The default is `denyOverrides`. */\n\treadonly algorithm?: Algorithm\n}\n\n/**\n * The error when a rule does not fit its domain: it names a kind, an action,\n * a field or a field of the principal that the domain does not have, or it\n * tests a field with a value that the field can not hold. It has one issue\n * for each problem, with its path, and a suggestion when a name has a mistake.\n *\n * @stability stable\n * @category errors\n * @since 2.0.0\n */\nexport const PolicyError: typeof Internal.PolicyError = Internal.PolicyError\n\n/**\n * @stability stable\n * @category errors\n * @since 2.0.0\n */\nexport type PolicyError = Internal.PolicyError\n\n/** The mark of a union of rules: `Condition.Opaque` when one has a predicate. */\ntype RulesOpaque<Rules> = Rules extends { readonly \"~Opaque\": () => infer X }\n\t? X\n\t: never\n\n/**\n * Makes a policy from a generator function that gives rules. It is pure and it\n * gives its result at once.\n *\n * @example\n * ```ts\n * const policy = Policy.define(App)(function* (_) {\n * yield* _.allow(\"read\", Document)\n * yield* _.allow([\"update\", \"delete\"], Document, {\n * when: ($) => $.eq(\"ownerId\", $.principal(\"id\")),\n * })\n * })\n * ```\n *\n * @stability stable\n * @category constructors\n * @since 0.3.0\n */\nexport const define: <D extends Domain.Any>(\n\tdomain: D,\n\toptions?: Options,\n) => <Rules extends Rule<D, never, any>, A>(\n\tbody: (builder: Builder<D>) => Generator<Rules, A, unknown>,\n) => Policy<D, RulesOpaque<Rules>> = Internal.define as never\n\ntype Combined<D extends Domain.Any, Ps extends ReadonlyArray<Any>> = Policy<\n\tD,\n\tOpaqueOf<Ps[number]>\n>\n\n/**\n * Every policy must allow, and one deny is enough to deny. With some policies\n * not applicable and none that deny, the result is not applicable.\n *\n * @stability stable\n * @category combinators\n * @since 0.3.0\n */\nexport const all: <const Ps extends readonly [Any, ...Array<Any>]>(\n\tpolicies: Ps,\n) => Combined<DomainOf<Ps[0]>, Ps> = Internal.combine(\"All\")\n\n/**\n * One allow is enough. Without an allow, one deny gives a deny.\n *\n * @stability stable\n * @category combinators\n * @since 0.3.0\n */\nexport const any: <const Ps extends readonly [Any, ...Array<Any>]>(\n\tpolicies: Ps,\n) => Combined<DomainOf<Ps[0]>, Ps> = Internal.combine(\"Any\")\n\n/**\n * The first policy that allows or denies gives the answer.\n *\n * @stability stable\n * @category combinators\n * @since 0.3.0\n */\nexport const firstOf: <const Ps extends readonly [Any, ...Array<Any>]>(\n\tpolicies: Ps,\n) => Combined<DomainOf<Ps[0]>, Ps> = Internal.combine(\"FirstOf\")\n\n/**\n * Puts a limit on a policy. A deny from `cap` always wins. An allow from `cap`\n * does nothing.\n *\n * @stability stable\n * @category combinators\n * @since 0.3.0\n */\nexport const ceiling: {\n\t<D extends Domain.Any, X2>(\n\t\tcap: Policy<D, X2>,\n\t): <X>(policy: Policy<D, X>) => Policy<D, X | X2>\n\t<D extends Domain.Any, X, X2>(\n\t\tpolicy: Policy<D, X>,\n\t\tcap: Policy<D, X2>,\n\t): Policy<D, X | X2>\n} = Internal.ceiling as never\n\n/**\n * Gives the services in `context` to every predicate of a policy, so the\n * policy needs no services when it runs.\n *\n * @stability stable\n * @category services\n * @since 1.0.0\n */\nexport const provideContext: {\n\t<R>(\n\t\tcontext: Context.Context<R>,\n\t): <D extends Domain.Any, X>(\n\t\tpolicy: Policy<D, X> &\n\t\t\t([Condition.ServicesOf<X>] extends [R] ? unknown : never),\n\t) => Policy<D, Condition.Provided<X>>\n\t<D extends Domain.Any, X>(\n\t\tpolicy: Policy<D, X>,\n\t\tcontext: Context.Context<Condition.ServicesOf<X>>,\n\t): Policy<D, Condition.Provided<X>>\n} = Internal.provideContext as never\n\n// Decisions\n\n/**\n * Gives the decision of a policy for a request.\n *\n * @stability stable\n * @category evaluation\n * @since 0.3.0\n */\nexport const decide: <\n\tD extends Domain.Any,\n\tX,\n\tconst Req extends DeepRequest<NoInfer<D>>,\n>(\n\tpolicy: Policy<D, X>,\n\trequest: Req & CheckField<NoInfer<D>, Req>,\n) => Effect.Effect<\n\tDecision.Decision,\n\tCondition.ErrorOf<X>,\n\tCondition.ServicesOf<X>\n> = Decide.decideAny as never\n\n/**\n * Gives the decision of a policy with no predicates, at once.\n *\n * @stability stable\n * @category evaluation\n * @since 0.3.0\n */\nexport const decideSync: <\n\tD extends Domain.Any,\n\tconst Req extends DeepRequest<NoInfer<D>>,\n>(\n\tpolicy: Policy<D>,\n\trequest: Req & CheckField<NoInfer<D>, Req>,\n) => Decision.Decision = Decide.decideSync\n\n/**\n * Gives the decision of a policy and the rules that were tested to make it.\n *\n * One node may be a child of many nodes. Its rules are tested, and are in the\n * steps, each time that the decision comes to it.\n *\n * @stability stable\n * @category evaluation\n * @since 0.3.0\n */\nexport const explain: <\n\tD extends Domain.Any,\n\tX,\n\tconst Req extends DeepRequest<NoInfer<D>>,\n>(\n\tpolicy: Policy<D, X>,\n\trequest: Req & CheckField<NoInfer<D>, Req>,\n) => Effect.Effect<Explanation, Condition.ErrorOf<X>, Condition.ServicesOf<X>> =\n\tDecide.explain as never\n\n/**\n * Gives the fields of a resource value that the policy lets a request use. It\n * tests each field that a list of fields shows: each scalar, record or array\n * of the resource Schema, with dots for nested fields.\n *\n * A field that holds more than one value, such as a record, is left out when a\n * deny rule names a part of it. The list can not name that part, and the whole\n * field would show it.\n *\n * @stability stable\n * @category evaluation\n * @since 0.3.0\n */\nexport const permittedFields: <D extends Domain.Any, X>(\n\tpolicy: Policy<D, X>,\n\trequest: FieldlessRequest<NoInfer<D>>,\n) => Effect.Effect<\n\tReadonlyArray<string>,\n\tCondition.ErrorOf<X>,\n\tCondition.ServicesOf<X>\n> = Decide.permittedFields as never\n\n// Residual conditions\n\n/**\n * What a residual condition reads: a value of the kind `K`. The principal is\n * already in it. A residual is for one kind, so when `K` is a union of kinds,\n * the value must be a value of each of them. No value is, so such a residual\n * can not be evaluated on a value of the wrong kind.\n *\n * @stability stable\n * @category utility types\n * @since 1.0.0\n */\nexport type ResidualReads<D, K> = Condition.Reads<\n\t(\n\t\tK extends unknown ? (value: Domain.ValueOf<D, K & string>) => void : never\n\t) extends (value: infer V) => void\n\t\t? V\n\t\t: never,\n\tunknown\n>\n\n/**\n * Gives the condition on resource values for which the policy allows a\n * request. It has no principal fields and no `Under` nodes, so you can give it\n * to `Condition.fold`. It is made smaller, so it is often `True` or `False`\n * when the answer does not depend on the value, but it does no search: a\n * condition that is true for every value may stay a test. To know if the\n * policy allows every value, or some value, use {@link coverage}.\n *\n * With a scope (`within`), the condition is the scope and the residual\n * together, so it is false for every value outside the scope. Give the policy\n * first to use a scope. The condition comes from its parts, with no search. To\n * know if it allows all of the scope or none of it, use {@link coverage}.\n *\n * When a rule that the request may use has a predicate, the condition is one\n * predicate with the name `\"policy\"`. It runs the whole policy for each value,\n * so its answers and its failures are the same as those of {@link decide}. It\n * takes the principal with it. Give it resource values with their `_tag`. It\n * is false for a value of another kind.\n *\n * A policy tree may use the condition of a child two times, so a small policy\n * may give a very large condition. When the condition would have more than\n * the `residualNodes` of the domain, 100000 by default, this function throws\n * an error. In an `Authorizer`, the error is a defect, so the check fails.\n *\n * @stability stable\n * @category evaluation\n * @since 0.3.0\n */\nexport const residual: <\n\tD extends Domain.Any,\n\tX,\n\tconst Req extends DeepKindRequest<NoInfer<D>>,\n>(\n\tpolicy: Policy<D, X>,\n\trequest: Req & CheckField<NoInfer<D>, Req>,\n) => Condition.Condition<ResidualReads<D, Req[\"kind\"]>, X> =\n\tAnalysis.residual as never\n\n/**\n * How much of a scope a policy allows. In each case, `condition` is the exact\n * condition on the values in the scope that the policy allows: give it to\n * `Condition.fold` for a list query.\n * - `All`: the policy allows every value in the scope. `condition` is the\n * scope (`True` with no scope).\n * - `None`: the policy allows no value in the scope. `condition` is `False`.\n * An empty scope is `None` too, so a check on a scope that has a mistake,\n * such as a principal field with no value, never passes.\n * - `Some`: the policy allows some values in the scope and not others.\n * `allowed` is an example of a value that it allows, and `refused` of a\n * value in the scope that it does not allow.\n * - `Unknown`: the answer needs a predicate, or the search went past its\n * budget.\n *\n * @stability stable\n * @category models\n * @since 1.0.0\n */\nexport type Coverage<I = unknown, X = never> = Data.TaggedEnum<{\n\tAll: { readonly condition: Condition.Condition<I, X> }\n\tNone: { readonly condition: Condition.Condition<I, X> }\n\tSome: {\n\t\treadonly condition: Condition.Condition<I, X>\n\t\treadonly allowed: Condition.Example\n\t\treadonly refused: Condition.Example\n\t}\n\tUnknown: {\n\t\treadonly condition: Condition.Condition<I, X>\n\t\treadonly reason: \"predicate\" | \"budget\"\n\t}\n}>\n\n/**\n * Tells how much of a scope the policy allows: all of it, none of it, or some\n * of it. See {@link Coverage}. With no scope (`within`), the scope is every\n * value of the kind.\n *\n * The answer comes from {@link Condition.satisfiable} with the shape of the\n * Schema of the resource. `All` and `None` hold for every value of the\n * Schema. `Some` means that, by the shape of the Schema, a value may get\n * either answer: a check such as a minimum length may still stop one of the\n * examples. `Unknown` comes only from a predicate or from the budget of the\n * search.\n *\n * `Some` does not mean that such a value is in your database. To know that,\n * run `condition` as a query.\n *\n * @stability stable\n * @category evaluation\n * @since 1.0.0\n */\nexport const coverage: <\n\tD extends Domain.Any,\n\tX,\n\tconst Req extends DeepKindRequest<NoInfer<D>>,\n>(\n\tpolicy: Policy<D, X>,\n\trequest: Req & CheckField<NoInfer<D>, Req>,\n) => Coverage<ResidualReads<D, Req[\"kind\"]>, X> = Analysis.coverageAny as never\n\n// Schema\n\n/**\n * The JSON form of a rule.\n *\n * @stability stable\n * @category models\n * @since 0.3.0\n */\nexport interface EncodedRule {\n\treadonly _tag: \"Allow\" | \"Deny\"\n\treadonly actions: ReadonlyArray<string>\n\treadonly resources: ReadonlyArray<string>\n\treadonly when?: Condition.Encoded\n\treadonly fields?: ReadonlyArray<string>\n\treadonly reason?: string\n\treadonly id?: string\n\treadonly meta?: unknown\n}\n\n/**\n * The JSON form of a policy.\n *\n * @stability stable\n * @category models\n * @since 0.3.0\n */\nexport type EncodedNode =\n\t| {\n\t\t\treadonly _tag: \"Rules\"\n\t\t\treadonly algorithm: Algorithm\n\t\t\treadonly rules: ReadonlyArray<EncodedRule>\n\t }\n\t| {\n\t\t\treadonly _tag: \"All\" | \"Any\" | \"FirstOf\"\n\t\t\treadonly policies: ReadonlyArray<EncodedNode>\n\t }\n\t| {\n\t\t\treadonly _tag: \"Ceiling\"\n\t\t\treadonly cap: EncodedNode\n\t\t\treadonly policy: EncodedNode\n\t }\n\n/**\n * The Schema of the policies of a domain with no predicates. Decoding checks\n * every kind, action, field and principal field against the domain, and the\n * `meta` of each rule against the `meta` Schema of the domain.\n *\n * The decoder takes the limits of the domain: at most `policyNodes` nodes\n * of the tree, `conditionNodes` nodes of each condition, and `jsonNodes`\n * objects and array items, with a shared object counted for each place. It\n * stops at the first one past a limit. It takes any number of levels, and it\n * reads only the own data fields of the input.\n *\n * @stability stable\n * @category schemas\n * @since 0.3.0\n */\nexport const schema: <D extends Domain.Any>(\n\tdomain: D,\n) => Schema.Codec<Policy<D>, EncodedNode> = Internal.schema as never\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAsnBA,MAAa,WAAiC,WAC7CA,UAAiB,MAAM;;;;;;;;;;;;;;;AAgBxB,MAAa,OAGgBC;;;;;;;;;;;AAwB7B,MAAa,cAA2CC;;;;;;;;;;;;;;;;;;;AAgCxD,MAAa,SAKwBC;;;;;;;;;AAerC,MAAa,MAEwBC,QAAiB,KAAK;;;;;;;;AAS3D,MAAa,MAEwBA,QAAiB,KAAK;;;;;;;;AAS3D,MAAa,UAEwBA,QAAiB,SAAS;;;;;;;;;AAU/D,MAAa,UAQTC;;;;;;;;;AAUJ,MAAa,iBAWTC;;;;;;;;AAWJ,MAAa,SAWTC;;;;;;;;AASJ,MAAa,aAMYC;;;;;;;;;;;AAYzB,MAAa,UAQZC;;;;;;;;;;;;;;AAeD,MAAa,kBAOTC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAmDJ,MAAa,WAQZC;;;;;;;;;;;;;;;;;;;;AAsDD,MAAa,WAOqCC;;;;;;;;;;;;;;;;AA4DlD,MAAa,SAE+BC"}
@@ -1 +1 @@
1
- {"version":3,"file":"catalog.d.ts","names":[],"sources":["../../src/internal/catalog.ts"],"mappings":";;;iBAsBiB;;WAEP;WACA;;WAEA;;WAEA;;WAEA;;WAEA,UAAU;;;iBA8EH;WACP;;WAEA,YAAY,cAAc,UAAU;;;YAIlC,mBAAmB;;iBAGd;WACP,MAAM,cAAc;;WAEpB;WACA,OAAO;;;;;WAKP;;WAEA;;;;;;;;;WASA;;;;;;;;iBASO;WACP;;WAEA,OAAO;;WAEP;;WAEA;WACA;WACA;;WAEA,UAAU;;;;;;;WAOV,OAAO,QAAQ;;WAEf,KAAK;WACL;;;iBAIO;;;;;;WAMP,QAAQ,cAAc;;WAEtB,UAAU,cAAc;;;;;WAKxB;WACA,MAAM"}
1
+ {"version":3,"file":"catalog.d.ts","names":[],"sources":["../../src/internal/catalog.ts"],"mappings":";;;iBAuBiB;;WAEP;WACA;;WAEA;;WAEA;;WAEA;;WAEA,UAAU;;;iBA8EH;WACP;;WAEA,YAAY,cAAc,UAAU;;;YAIlC,mBAAmB;;iBAGd;WACP,MAAM,cAAc;;WAEpB;WACA,OAAO;;;;;WAKP;;WAEA;;;;;;;;;WASA;;;;;;;;iBASO;WACP;;WAEA,OAAO;;WAEP;;WAEA;WACA;WACA;;WAEA,UAAU;;;;;;;WAOV,OAAO,QAAQ;;WAEf,KAAK;WACL;;;iBAIO;;;;;;WAMP,QAAQ,cAAc;;WAEtB,UAAU,cAAc;;;;;WAKxB;WACA,MAAM"}
@@ -1,4 +1,5 @@
1
1
  import { ceilings, exceeded, fieldPathParts } from "./limits.js";
2
+ import { adaptive } from "./trampoline.js";
2
3
  import * as Arr from "effect/Array";
3
4
  import * as HashMap from "effect/HashMap";
4
5
  import * as HashSet from "effect/HashSet";
@@ -111,13 +112,28 @@ const classStruct = (ast) => {
111
112
  * Schema nodes by structure, and a recursive Schema has no end. A suspend
112
113
  * node that gives itself again, with no step on the path, gives nothing.
113
114
  */
114
- const alternatives = (ast, open) => {
115
+ const alternatives = (ast, open) => alternativesOf({
116
+ ast,
117
+ open
118
+ });
119
+ /**
120
+ * The walk of {@link alternatives}. A union may hold a union, very many
121
+ * levels down, so it is a walk of the trampoline for an input of an unknown
122
+ * depth.
123
+ */
124
+ const alternativesOf = adaptive((R, child) => ({ ast, open }, depth) => {
115
125
  switch (ast._tag) {
116
- case "Union": return Arr.flatMap(ast.types, (type) => alternatives(type, open));
117
- case "Suspend": return open.has(ast) ? [] : alternatives(ast.thunk(), /* @__PURE__ */ new Set([...open, ast]));
118
- default: return [ast];
126
+ case "Union": return R.map(R.forEach(ast.types, (type) => child({
127
+ ast: type,
128
+ open
129
+ }, depth + 1)), Arr.flatten);
130
+ case "Suspend": return open.has(ast) ? R.succeed([]) : child({
131
+ ast: ast.thunk(),
132
+ open: /* @__PURE__ */ new Set([...open, ast])
133
+ }, depth + 1);
134
+ default: return R.succeed([ast]);
119
135
  }
120
- };
136
+ });
121
137
  /** The struct of a Schema: a struct, or the struct of a class. */
122
138
  const structOf = (ast) => ast._tag === "Objects" ? ast : ast._tag === "Declaration" ? classStruct(ast) : void 0;
123
139
  const hasCheck = (checks, id) => Arr.some(checks, (check) => check.annotations?.representation?.id === id || check._tag === "FilterGroup" && hasCheck(check.checks, id));
@@ -322,7 +338,9 @@ const listOf = (branches, parts) => {
322
338
  return Arr.flatten(Arr.unfold(stack, (rest) => {
323
339
  const visit = rest.pop();
324
340
  if (visit === void 0) return Option.none();
325
- rest.push(...Arr.reverse(below(visit)));
341
+ Arr.forEach(Arr.reverse(below(visit)), (next) => {
342
+ rest.push(next);
343
+ });
326
344
  const field = visit.path.length === 0 ? [] : [fieldAt(visit.branch, visit.path, parts)];
327
345
  if (field.length > 0 && rest.length + 1 > ceilings.catalogFields) throw exceeded("A Schema", "catalogFields", "fields", rest.length + 1, ceilings.catalogFields);
328
346
  return Option.some([field, rest]);
@@ -1 +1 @@
1
- {"version":3,"file":"catalog.js","names":["Limits.exceeded","Limits.fieldPathParts"],"sources":["../../src/internal/catalog.ts"],"sourcesContent":["/**\n * The catalog of the fields of a Schema. One walk over the Schema makes it,\n * and every question about the fields of a resource or of the principal reads\n * it: which names a rule may use, which values a field may hold, which fields\n * a pattern fits, and which fields a list of permitted fields shows.\n *\n * The catalog describes the shape of the values, not the checks of the\n * Schema. A check, such as a minimum length, only keeps some values out, so a\n * catalog that leaves the checks out holds every real value and more. An\n * answer that holds for all of these values, as `All` and `None` of a\n * coverage do, then holds for the real values too.\n */\nimport * as Arr from \"effect/Array\"\nimport * as HashMap from \"effect/HashMap\"\nimport * as HashSet from \"effect/HashSet\"\nimport * as Option from \"effect/Option\"\nimport { hasProperty } from \"effect/Predicate\"\nimport * as Schema from \"effect/Schema\"\nimport * as SchemaAST from \"effect/SchemaAST\"\nimport * as Limits from \"./limits.ts\"\n\n/** The kinds of values that a field may hold. */\nexport interface Kinds {\n\t/** The field may be `null`, `undefined` or missing. */\n\treadonly null: boolean\n\treadonly boolean: boolean\n\t/** The field may hold any string. */\n\treadonly string: boolean\n\t/** The numbers that the field may hold. */\n\treadonly number: \"none\" | \"integer\" | \"finite\" | \"any\"\n\t/** The field may hold an object, or a value that is not a scalar. */\n\treadonly object: boolean\n\t/** Single values that the field may hold, other than the ones above. */\n\treadonly literals: ReadonlyArray<string | number | boolean>\n}\n\n/** No value: the kinds of a field of `Schema.Never`. */\nexport const noKinds: Kinds = {\n\tnull: false,\n\tboolean: false,\n\tstring: false,\n\tnumber: \"none\",\n\tobject: false,\n\tliterals: [],\n}\n\n/** Any value. */\nexport const anyKinds: Kinds = {\n\tnull: true,\n\tboolean: true,\n\tstring: true,\n\tnumber: \"any\",\n\tobject: true,\n\tliterals: [],\n}\n\nconst nullKinds: Kinds = { ...noKinds, null: true }\nconst objectKinds: Kinds = { ...noKinds, object: true }\n\nconst numberOrder = [\"none\", \"integer\", \"finite\", \"any\"] as const\n\n/** The kinds of a field that may hold a value of `left` or of `right`. */\nexport const joinKinds = (left: Kinds, right: Kinds): Kinds => ({\n\tnull: left.null || right.null,\n\tboolean: left.boolean || right.boolean,\n\tstring: left.string || right.string,\n\tnumber:\n\t\tnumberOrder.indexOf(left.number) >= numberOrder.indexOf(right.number)\n\t\t\t? left.number\n\t\t\t: right.number,\n\tobject: left.object || right.object,\n\t// A native `Set` finds copies about ten times as fast as `Arr.dedupe`. It\n\t// takes `0` and `-0` as one, as `includes` does when a test reads them.\n\tliterals: Array.from(new Set([...left.literals, ...right.literals])),\n})\n\n/** Tests if a number fits the numbers of a field. */\nexport const fitsNumber = (number: Kinds[\"number\"], value: number): boolean => {\n\tswitch (number) {\n\t\tcase \"none\":\n\t\t\treturn false\n\t\tcase \"integer\":\n\t\t\treturn Number.isInteger(value)\n\t\tcase \"finite\":\n\t\t\treturn Number.isFinite(value)\n\t\tcase \"any\":\n\t\t\treturn true\n\t}\n}\n\n/**\n * Tests if a field with these kinds may hold a value. A missing value and\n * `undefined` are `null`.\n */\nexport const accepts = (kinds: Kinds, value: unknown): boolean => {\n\tif (value === null || value === undefined) {\n\t\treturn kinds.null\n\t}\n\tswitch (typeof value) {\n\t\tcase \"boolean\":\n\t\t\treturn kinds.boolean || kinds.literals.includes(value)\n\t\tcase \"string\":\n\t\t\treturn kinds.string || kinds.literals.includes(value)\n\t\tcase \"number\":\n\t\t\treturn fitsNumber(kinds.number, value) || kinds.literals.includes(value)\n\t\tdefault:\n\t\t\treturn kinds.object\n\t}\n}\n\n/** A part of a field path that stands for any key of a record. */\nexport interface Key {\n\treadonly _tag: \"Key\"\n\t/** The Schemas of the keys. A key must fit one of them. */\n\treadonly parameters: ReadonlyArray<SchemaAST.AST>\n}\n\n/** One part of a field path: a name, or any key of a record. */\nexport type Segment = string | Key\n\n/** One field of a catalog. */\nexport interface Field {\n\treadonly path: ReadonlyArray<Segment>\n\t/** The path with dots, and `*` for a key of a record. */\n\treadonly text: string\n\treadonly kinds: Kinds\n\t/**\n\t * The field holds a value that a list of fields shows as one field: a\n\t * scalar, a record, an array, or a struct at the last part a path may have.\n\t */\n\treadonly listed: boolean\n\t/** The field may hold parts that the Schema does not name, as a record may. */\n\treadonly open: boolean\n\t/**\n\t * A read of the field finds only plain data. It is `false` when a value\n\t * above the field may be an object that does not have it as a data field:\n\t * a class without the field, which may have a getter of that name, a\n\t * value such as a date or a list, or any object, as of `Schema.Unknown`.\n\t * A struct of a union without the field makes it not plain too, since a\n\t * value made by hand may have it with any value.\n\t */\n\treadonly plain: boolean\n}\n\n/**\n * A place in the graph of a catalog. A path of a field leads to a place, and\n * many paths may lead to one place, as in a recursive Schema: `next` and\n * `next.next` of a list of links are one place. A place keeps the ids of the\n * places below it, so the graph has no loop of objects.\n */\nexport interface Branch {\n\treadonly id: number\n\t/** The kinds of the values of the field at this place. */\n\treadonly kinds: Kinds\n\t/** The field holds a scalar, a record or a list: see `Field.listed`. */\n\treadonly values: boolean\n\t/** The field may hold a struct. */\n\treadonly structs: boolean\n\treadonly open: boolean\n\treadonly plain: boolean\n\t/** The ids of the places below, by name, in the order of the Schema. */\n\treadonly children: ReadonlyArray<readonly [string, number]>\n\t/**\n\t * The places below, by name. A lookup gets each name from `split`, as a\n\t * new string, and a `HashMap` finds a new string about 20% faster than a\n\t * native `Map`. The names come from the Schema, so a name from a policy\n\t * can not make a long list of names with one hash here.\n\t */\n\treadonly names: HashMap.HashMap<string, number>\n\t/** The place for the keys of a record, when the value may be a record. */\n\treadonly key: Key | undefined\n\treadonly keyBranch: number | undefined\n}\n\n/** The catalog of the fields of a Schema. */\nexport interface Catalog {\n\t/**\n\t * The fields that a list shows, a field before the fields below it. For a\n\t * recursive Schema, the list goes into each recursive place one time on\n\t * each path, so it is finite.\n\t */\n\treadonly fields: ReadonlyArray<Field>\n\t/** The places, by id. The root is the place with the id 0. */\n\treadonly branches: ReadonlyArray<Branch>\n\t/**\n\t * The number of steps from a place to a place below it. A search of the\n\t * catalog, as for a field pattern, reads each one, so this is its work.\n\t */\n\treadonly edges: number\n\treadonly root: Branch\n}\n\n/**\n * Tests if a key of an object fits the Schemas of the keys of a record. A key\n * of an object is a string, also a key that a number Schema takes: `{ 1: \"a\" }`\n * has the key `\"1\"`. So a key that is the text of a number is tried as that\n * number too.\n */\nexport const keyFits = (key: Key, name: string): boolean =>\n\tArr.some(key.parameters, (parameter) => {\n\t\tconst is = Schema.is(Schema.make(parameter))\n\t\treturn (\n\t\t\tis(name) ||\n\t\t\t(name !== \"\" && String(Number(name)) === name && is(Number(name)))\n\t\t)\n\t})\n\n/**\n * Gives the struct of a `Schema.Class`. A class is a declaration with one\n * struct as its type parameter, and Effect marks it with a `\"~constructor\"`\n * annotation, also through more codec steps, as in\n * `Schema.fromJsonString(User)`. Other declarations, such as `Schema.Option`\n * or `Schema.ReadonlyMap`, have no such mark, and the fields of their type\n * parameter are not fields of the value.\n */\nconst classStruct = (\n\tast: SchemaAST.Declaration,\n): SchemaAST.Objects | undefined => {\n\tconst [inner] = ast.typeParameters\n\treturn ast.annotations?.[\"~constructor\"] !== undefined &&\n\t\tast.typeParameters.length === 1 &&\n\t\tinner?._tag === \"Objects\"\n\t\t? inner\n\t\t: undefined\n}\n\n/**\n * Gives the Schemas that a value at one place may fit: it opens unions and\n * `Schema.suspend`. A class stays a class: see {@link structOf}. The open suspend nodes are in a native `Set`,\n * because the walk must find a node by the node itself: `Equal` compares\n * Schema nodes by structure, and a recursive Schema has no end. A suspend\n * node that gives itself again, with no step on the path, gives nothing.\n */\nconst alternatives = (\n\tast: SchemaAST.AST,\n\topen: ReadonlySet<SchemaAST.AST>,\n): ReadonlyArray<SchemaAST.AST> => {\n\tswitch (ast._tag) {\n\t\tcase \"Union\":\n\t\t\treturn Arr.flatMap(ast.types, (type) => alternatives(type, open))\n\t\tcase \"Suspend\":\n\t\t\treturn open.has(ast)\n\t\t\t\t? []\n\t\t\t\t: alternatives(ast.thunk(), new Set([...open, ast]))\n\t\tdefault:\n\t\t\treturn [ast]\n\t}\n}\n\n/** The struct of a Schema: a struct, or the struct of a class. */\nconst structOf = (ast: SchemaAST.AST): SchemaAST.Objects | undefined =>\n\tast._tag === \"Objects\"\n\t\t? ast\n\t\t: ast._tag === \"Declaration\"\n\t\t\t? classStruct(ast)\n\t\t\t: undefined\n\nconst hasCheck = (\n\tchecks: ReadonlyArray<SchemaAST.Check<never>>,\n\tid: string,\n): boolean =>\n\tArr.some(\n\t\tchecks,\n\t\t(check) =>\n\t\t\tcheck.annotations?.representation?.id === id ||\n\t\t\t(check._tag === \"FilterGroup\" && hasCheck(check.checks, id)),\n\t)\n\n/** A struct with named fields, or with the keys of a record. */\nconst isStruct = (\n\tast: SchemaAST.Objects | undefined,\n): ast is SchemaAST.Objects =>\n\tast !== undefined &&\n\t(ast.propertySignatures.length > 0 || ast.indexSignatures.length > 0)\n\n/** The kinds of the values of a Schema that is not a struct. */\nconst kindsOf = (ast: SchemaAST.AST): Kinds => {\n\tswitch (ast._tag) {\n\t\tcase \"Null\":\n\t\tcase \"Undefined\":\n\t\t\treturn nullKinds\n\t\tcase \"Never\":\n\t\t\treturn noKinds\n\t\tcase \"String\":\n\t\tcase \"TemplateLiteral\":\n\t\t\treturn { ...noKinds, string: true }\n\t\tcase \"Boolean\":\n\t\t\treturn { ...noKinds, boolean: true }\n\t\tcase \"Number\": {\n\t\t\t// The two checks that a search uses. Effect names a number with\n\t\t\t// them, as `Schema.Int` and `Schema.Finite`; other checks are left\n\t\t\t// out, as all checks are.\n\t\t\tconst checks = ast.checks ?? []\n\t\t\treturn {\n\t\t\t\t...noKinds,\n\t\t\t\tnumber: hasCheck(checks, \"effect/schema/isInt\")\n\t\t\t\t\t? \"integer\"\n\t\t\t\t\t: hasCheck(checks, \"effect/schema/isFinite\")\n\t\t\t\t\t\t? \"finite\"\n\t\t\t\t\t\t: \"any\",\n\t\t\t}\n\t\t}\n\t\tcase \"Literal\":\n\t\t\treturn typeof ast.literal === \"bigint\"\n\t\t\t\t? objectKinds\n\t\t\t\t: { ...noKinds, literals: [ast.literal] }\n\t\tcase \"Enum\":\n\t\t\treturn { ...noKinds, literals: Arr.map(ast.enums, ([, value]) => value) }\n\t\tcase \"Arrays\":\n\t\tcase \"ObjectKeyword\":\n\t\tcase \"BigInt\":\n\t\tcase \"Symbol\":\n\t\tcase \"UniqueSymbol\":\n\t\t\treturn objectKinds\n\t\tcase \"Declaration\":\n\t\t\t// A declaration of Effect in `objectDeclarations` holds an object,\n\t\t\t// such as a `Date`. Another declaration may hold any value, as\n\t\t\t// `Schema.Json` or one from `Schema.declare` may.\n\t\t\treturn HashSet.has(objectDeclarations, declarationId(ast))\n\t\t\t\t? objectKinds\n\t\t\t\t: anyKinds\n\t\tdefault:\n\t\t\t// `Schema.Unknown`, `Schema.Void`, and `Struct({})`, which takes a\n\t\t\t// string too: these may hold any value.\n\t\t\treturn anyKinds\n\t}\n}\n\n/**\n * The ids of the declarations of Effect that hold only objects. Effect puts\n * the id in the `representation` annotation. A declaration that is not in\n * this list may hold a scalar, as `Schema.Json` may, so its kinds are any\n * value.\n */\nconst objectDeclarations = HashSet.make(\n\t\"effect/schema/Date\",\n\t\"effect/schema/URL\",\n\t\"effect/schema/Uint8Array\",\n\t\"effect/schema/Duration\",\n\t\"effect/schema/DateTimeUtc\",\n\t\"effect/schema/DateTimeZoned\",\n\t\"effect/schema/BigDecimal\",\n\t\"effect/schema/Redacted\",\n\t\"effect/schema/Cause\",\n\t\"effect/schema/Chunk\",\n\t\"effect/schema/HashMap\",\n\t\"effect/schema/HashSet\",\n\t\"effect/schema/ReadonlyMap\",\n\t\"effect/schema/ReadonlySet\",\n\t\"effect/schema/Option\",\n\t\"effect/schema/Result\",\n\t\"effect/schema/Exit\",\n\t\"effect/schema/File\",\n\t\"effect/schema/FormData\",\n\t\"effect/schema/RegExp\",\n\t\"effect/schema/URLSearchParams\",\n)\n\nconst declarationId = (ast: SchemaAST.Declaration): string | undefined => {\n\tconst representation: unknown = ast.annotations?.[\"representation\"]\n\treturn hasProperty(representation, \"id\") &&\n\t\ttypeof representation.id === \"string\"\n\t\t? representation.id\n\t\t: undefined\n}\n\n/** The things that the walk of a catalog knows at one place. */\ninterface Place {\n\treadonly schemas: ReadonlyArray<SchemaAST.AST>\n\t/** The value may be missing. */\n\treadonly missing: boolean\n\t/** The value may be anything, as an extra field of a struct may be. */\n\treadonly top: boolean\n\t/** A read here finds only plain data: see {@link Field.plain}. */\n\treadonly plain: boolean\n}\n\nconst textOf = (path: ReadonlyArray<Segment>): string =>\n\tArr.map(path, (segment) =>\n\t\ttypeof segment === \"string\" ? segment : \"*\",\n\t).join(\".\")\n\n/**\n * The name of a property as a part of a path. A key of an object is a string,\n * so a number key, as from `Schema.Record(Schema.Literal(1), ...)`, has the\n * name `\"1\"`. A symbol key is not a field.\n */\nconst nameOf = (signature: SchemaAST.PropertySignature): string | undefined =>\n\ttypeof signature.name === \"symbol\" ? undefined : String(signature.name)\n\n/**\n * The properties of a struct by name. A walk makes it one time for each\n * struct of a place, so a struct with very many fields does not take a\n * search for each name. It is a native `Map`, as `Branch.names` is.\n */\nconst propertiesOf = (\n\tstruct: SchemaAST.Objects,\n): ReadonlyMap<string, SchemaAST.PropertySignature> =>\n\tnew Map(\n\t\tArr.flatMap(struct.propertySignatures, (signature) => {\n\t\t\tconst name = nameOf(signature)\n\t\t\treturn name === undefined ? [] : [[name, signature] as const]\n\t\t}),\n\t)\n\n/** The names of the fields of the structs, in the order that they first come. */\nconst namesOf = (\n\tstructs: ReadonlyArray<SchemaAST.Objects>,\n): ReadonlyArray<string> =>\n\t// A native `Set` keeps the first copy of each name, in order, about ten\n\t// times as fast as `Arr.dedupe`.\n\tArray.from(\n\t\tnew Set(\n\t\t\tArr.flatMap(structs, (struct) =>\n\t\t\t\tArr.flatMap(struct.propertySignatures, (signature) => {\n\t\t\t\t\tconst name = nameOf(signature)\n\t\t\t\t\tif (name === undefined) {\n\t\t\t\t\t\treturn []\n\t\t\t\t\t}\n\t\t\t\t\t// A dot in a path is the step into a nested field. A field name with\n\t\t\t\t\t// a dot would make two fields have one path, so it is a defect.\n\t\t\t\t\tif (name.includes(\".\")) {\n\t\t\t\t\t\tthrow new Error(`A field name may not have a dot: \"${name}\"`)\n\t\t\t\t\t}\n\t\t\t\t\t// An empty name would make an empty part in a path, such as\n\t\t\t\t\t// `.secret`.\n\t\t\t\t\tif (name === \"\") {\n\t\t\t\t\t\tthrow new Error(\"A field name may not be empty\")\n\t\t\t\t\t}\n\t\t\t\t\treturn [name]\n\t\t\t\t}),\n\t\t\t),\n\t\t),\n\t)\n\n/** A place of the walk, and the places below it, before they have ids. */\ninterface Made {\n\treadonly branch: Omit<Branch, \"id\" | \"children\" | \"names\" | \"keyBranch\">\n\treadonly children: ReadonlyArray<readonly [string, Place]>\n\treadonly keyPlace: Place | undefined\n}\n\n/** Works out the facts of a place, and the places below it. */\nconst placeOf = (place: Place): Made => {\n\tconst structs = Arr.filter(Arr.map(place.schemas, structOf), isStruct)\n\tconst values = Arr.filter(\n\t\tplace.schemas,\n\t\t(schema) => !isStruct(structOf(schema)),\n\t)\n\tconst valueKinds = Arr.reduce(values, noKinds, (kinds, schema) =>\n\t\tjoinKinds(kinds, kindsOf(structOf(schema) ?? schema)),\n\t)\n\tconst kinds = place.top\n\t\t? anyKinds\n\t\t: joinKinds(\n\t\t\t\tjoinKinds(valueKinds, structs.length > 0 ? objectKinds : noKinds),\n\t\t\t\tplace.missing ? nullKinds : noKinds,\n\t\t\t)\n\tconst records = Arr.filter(\n\t\tstructs,\n\t\t(struct) => struct.indexSignatures.length > 0,\n\t)\n\tconst structProperties = Arr.map(structs, propertiesOf)\n\tconst children = Arr.map(namesOf(structs), (name) => {\n\t\tconst properties = Arr.flatMap(structProperties, (named) => {\n\t\t\tconst property = named.get(name)\n\t\t\treturn property === undefined ? [] : [property]\n\t\t})\n\t\t// A struct without the name may still have it as an extra field, with\n\t\t// any value: only a decode or a `make` takes extra fields away, and a\n\t\t// decision may get a value made by hand. A value that may be any\n\t\t// object, as one of `Schema.Unknown` may, may have the name with any\n\t\t// value too.\n\t\tconst top =\n\t\t\tplace.top || valueKinds.object || properties.length < structs.length\n\t\tconst below: Place = {\n\t\t\tschemas: Arr.flatMap(properties, (property) =>\n\t\t\t\talternatives(property.type, new Set()),\n\t\t\t),\n\t\t\t// A field below a value that is not a struct reads `null`, and so\n\t\t\t// does a field that may be missing.\n\t\t\tmissing:\n\t\t\t\tplace.missing ||\n\t\t\t\tvalues.length > 0 ||\n\t\t\t\tArr.some(properties, (property) => SchemaAST.isOptional(property.type)),\n\t\t\ttop,\n\t\t\t// A value that may be anything may be a list, or have a getter, so\n\t\t\t// a read below it may not find plain data.\n\t\t\tplain: place.plain && !top,\n\t\t}\n\t\treturn [name, below] as const\n\t})\n\tconst signatures = Arr.flatMap(records, (struct) => struct.indexSignatures)\n\tconst keyTop =\n\t\tplace.top || valueKinds.object || records.length < structs.length\n\treturn {\n\t\tbranch: {\n\t\t\tkinds,\n\t\t\tvalues: values.length > 0 || records.length > 0,\n\t\t\tstructs: structs.length > 0,\n\t\t\t// A field that is a scalar in one branch of a union and a struct in\n\t\t\t// another holds parts too.\n\t\t\topen:\n\t\t\t\trecords.length > 0 ||\n\t\t\t\tstructs.length > 0 ||\n\t\t\t\tvalueKinds.object ||\n\t\t\t\tplace.top,\n\t\t\tplain: place.plain,\n\t\t\tkey:\n\t\t\t\tsignatures.length === 0\n\t\t\t\t\t? undefined\n\t\t\t\t\t: {\n\t\t\t\t\t\t\t_tag: \"Key\",\n\t\t\t\t\t\t\tparameters: Arr.map(\n\t\t\t\t\t\t\t\tsignatures,\n\t\t\t\t\t\t\t\t(signature) => signature.parameter,\n\t\t\t\t\t\t\t),\n\t\t\t\t\t\t},\n\t\t},\n\t\tchildren,\n\t\tkeyPlace:\n\t\t\tsignatures.length === 0\n\t\t\t\t? undefined\n\t\t\t\t: {\n\t\t\t\t\t\tschemas: Arr.flatMap(signatures, (signature) =>\n\t\t\t\t\t\t\talternatives(signature.type, new Set()),\n\t\t\t\t\t\t),\n\t\t\t\t\t\t// A record may not have a given key.\n\t\t\t\t\t\tmissing: true,\n\t\t\t\t\t\ttop: keyTop,\n\t\t\t\t\t\tplain: place.plain && !keyTop,\n\t\t\t\t\t},\n\t}\n}\n\n/**\n * Makes the places of a Schema. Each place comes one time: two paths with the\n * same Schemas and the same marks lead to one place. The walk keeps a queue\n * of the places that it has found, and it makes them in order, so a deep\n * Schema does not fill the stack.\n */\nconst branchesOf = (root: Place): ReadonlyArray<Branch> => {\n\t// The id of each Schema node, and of each place, for this walk. The ids\n\t// are in native maps, because the walk must find a Schema node by the node\n\t// itself: `Equal` compares Schema nodes by structure, and a recursive\n\t// Schema has no end. The maps and the queue belong to this walk.\n\tconst astIds = new Map<SchemaAST.AST, number>()\n\tconst placeIds = new Map<string, number>()\n\tconst queue: Array<Place> = []\n\tconst idOf = (place: Place): number => {\n\t\tconst ids = Arr.map(place.schemas, (schema) => {\n\t\t\tconst known = astIds.get(schema)\n\t\t\tif (known !== undefined) {\n\t\t\t\treturn known\n\t\t\t}\n\t\t\tastIds.set(schema, astIds.size)\n\t\t\treturn astIds.size - 1\n\t\t})\n\t\tconst key = `${Array.from(new Set(ids))\n\t\t\t.sort((a, b) => a - b)\n\t\t\t.join(\n\t\t\t\t\",\",\n\t\t\t)}|${String(place.missing)}|${String(place.top)}|${String(place.plain)}`\n\t\tconst known = placeIds.get(key)\n\t\tif (known !== undefined) {\n\t\t\treturn known\n\t\t}\n\t\tif (queue.length >= Limits.ceilings.catalogFields) {\n\t\t\tthrow Limits.exceeded(\n\t\t\t\t\"A Schema\",\n\t\t\t\t\"catalogFields\",\n\t\t\t\t\"places\",\n\t\t\t\tqueue.length + 1,\n\t\t\t\tLimits.ceilings.catalogFields,\n\t\t\t)\n\t\t}\n\t\tplaceIds.set(key, queue.length)\n\t\tqueue.push(place)\n\t\treturn queue.length - 1\n\t}\n\tidOf(root)\n\treturn Arr.unfold(0, (id) => {\n\t\tconst place = queue[id]\n\t\tif (place === undefined) {\n\t\t\treturn Option.none()\n\t\t}\n\t\tconst made = placeOf(place)\n\t\tconst children = Arr.map(\n\t\t\tmade.children,\n\t\t\t([name, below]) => [name, idOf(below)] as const,\n\t\t)\n\t\tconst branch: Branch = {\n\t\t\t...made.branch,\n\t\t\tid,\n\t\t\tchildren,\n\t\t\tnames: HashMap.fromIterable(children),\n\t\t\tkeyBranch: made.keyPlace === undefined ? undefined : idOf(made.keyPlace),\n\t\t}\n\t\treturn Option.some([branch, id + 1] as const)\n\t})\n}\n\n/** The field at a place, for a path. */\nconst fieldAt = (\n\tbranch: Branch,\n\tpath: ReadonlyArray<Segment>,\n\tparts: number,\n): Field => ({\n\tpath,\n\ttext: textOf(path),\n\tkinds: branch.kinds,\n\t// A struct at the last part that a path may have is one field, since a\n\t// path does not go below it.\n\tlisted: branch.values || (path.length === parts && branch.structs),\n\topen: branch.open,\n\tplain: branch.plain,\n})\n\n/**\n * The fields that a list shows, a field before the fields below it, down to\n * `parts` parts. A path goes into a place that is on it already one time\n * only, so the list of a recursive Schema is finite. The walk keeps a stack\n * of the places to visit, so a deep Schema does not fill the stack. The\n * stack belongs to this walk.\n */\nconst listOf = (\n\tbranches: ReadonlyArray<Branch>,\n\tparts: number,\n): ReadonlyArray<Field> => {\n\tinterface Visit {\n\t\treadonly branch: Branch\n\t\treadonly path: ReadonlyArray<Segment>\n\t\t/** The ids of the places on the path, as a set of Effect that shares its parts. */\n\t\treadonly above: HashSet.HashSet<number>\n\t}\n\tconst below = (visit: Visit): ReadonlyArray<Visit> => {\n\t\tconst { branch, path } = visit\n\t\tif (\n\t\t\tpath.length === parts ||\n\t\t\t(path.length > 0 && HashSet.has(visit.above, branch.id))\n\t\t) {\n\t\t\treturn []\n\t\t}\n\t\tconst above = HashSet.add(visit.above, branch.id)\n\t\tconst named = Arr.map(branch.children, ([name, id]) => ({\n\t\t\tbranch: branches[id]!,\n\t\t\tpath: [...path, name],\n\t\t\tabove,\n\t\t}))\n\t\treturn branch.key === undefined || branch.keyBranch === undefined\n\t\t\t? named\n\t\t\t: [\n\t\t\t\t\t...named,\n\t\t\t\t\t{\n\t\t\t\t\t\tbranch: branches[branch.keyBranch]!,\n\t\t\t\t\t\tpath: [...path, branch.key],\n\t\t\t\t\t\tabove,\n\t\t\t\t\t},\n\t\t\t\t]\n\t}\n\tconst stack: Array<Visit> = [\n\t\t{ branch: branches[0]!, path: [], above: HashSet.empty() },\n\t]\n\treturn Arr.flatten(\n\t\tArr.unfold(stack, (rest) => {\n\t\t\tconst visit = rest.pop()\n\t\t\tif (visit === undefined) {\n\t\t\t\treturn Option.none()\n\t\t\t}\n\t\t\trest.push(...Arr.reverse(below(visit)))\n\t\t\tconst field =\n\t\t\t\tvisit.path.length === 0\n\t\t\t\t\t? []\n\t\t\t\t\t: [fieldAt(visit.branch, visit.path, parts)]\n\t\t\tif (field.length > 0 && rest.length + 1 > Limits.ceilings.catalogFields) {\n\t\t\t\tthrow Limits.exceeded(\n\t\t\t\t\t\"A Schema\",\n\t\t\t\t\t\"catalogFields\",\n\t\t\t\t\t\"fields\",\n\t\t\t\t\trest.length + 1,\n\t\t\t\t\tLimits.ceilings.catalogFields,\n\t\t\t\t)\n\t\t\t}\n\t\t\treturn Option.some([field, rest] as const)\n\t\t}),\n\t)\n}\n\n/**\n * The fields that a list shows for paths of at most `parts` parts, as for a\n * domain with that limit. See `Catalog.fields`.\n */\nexport const fieldsOf = (\n\tcatalog: Catalog,\n\tparts: number,\n): ReadonlyArray<Field> =>\n\tparts === Limits.fieldPathParts\n\t\t? catalog.fields\n\t\t: listOf(catalog.branches, parts)\n\n/**\n * Makes the catalog of a Schema, with the list of its fields for the default\n * limit of the parts of a path. It throws when a field name has a dot or is\n * empty, and when the Schema has more places or fields than the ceiling of\n * `catalogFields`. A domain checks the size of the list with its own limit.\n */\nexport const catalogOf = (ast: SchemaAST.AST): Catalog => {\n\tconst branches = branchesOf({\n\t\tschemas: alternatives(ast, new Set()),\n\t\tmissing: false,\n\t\ttop: false,\n\t\tplain: true,\n\t})\n\treturn {\n\t\tfields: listOf(branches, Limits.fieldPathParts),\n\t\tbranches,\n\t\tedges: Arr.reduce(\n\t\t\tbranches,\n\t\t\t0,\n\t\t\t(total, branch) =>\n\t\t\t\ttotal +\n\t\t\t\tbranch.children.length +\n\t\t\t\t(branch.keyBranch === undefined ? 0 : 1),\n\t\t),\n\t\troot: branches[0]!,\n\t}\n}\n\n/**\n * Gives the field of a catalog at a path with dots, if the catalog has it.\n * A path has at most `parts` parts. The lookup follows one place for each\n * part, in a loop.\n */\nexport const lookup = (\n\tcatalog: Catalog,\n\tpath: string,\n\tparts: number = Limits.fieldPathParts,\n): Field | undefined => {\n\tconst names = path.split(\".\", parts + 1)\n\tif (names.length > parts) {\n\t\treturn undefined\n\t}\n\tconst found = Arr.reduce(\n\t\tnames,\n\t\t{ branch: catalog.root, path: [] } as\n\t\t\t| { readonly branch: Branch; readonly path: ReadonlyArray<Segment> }\n\t\t\t| undefined,\n\t\t(current, name) => {\n\t\t\tif (current === undefined) {\n\t\t\t\treturn undefined\n\t\t\t}\n\t\t\tconst named = HashMap.get(current.branch.names, name)\n\t\t\tif (Option.isSome(named)) {\n\t\t\t\treturn {\n\t\t\t\t\tbranch: catalog.branches[named.value]!,\n\t\t\t\t\tpath: [...current.path, name],\n\t\t\t\t}\n\t\t\t}\n\t\t\tconst key = current.branch.key\n\t\t\tconst keyBranch = current.branch.keyBranch\n\t\t\treturn key !== undefined && keyBranch !== undefined && keyFits(key, name)\n\t\t\t\t? { branch: catalog.branches[keyBranch]!, path: [...current.path, key] }\n\t\t\t\t: undefined\n\t\t},\n\t)\n\treturn found === undefined\n\t\t? undefined\n\t\t: fieldAt(found.branch, found.path, parts)\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"catalog.js","names":["Trampoline.adaptive","Limits.exceeded","Limits.fieldPathParts"],"sources":["../../src/internal/catalog.ts"],"sourcesContent":["/**\n * The catalog of the fields of a Schema. One walk over the Schema makes it,\n * and every question about the fields of a resource or of the principal reads\n * it: which names a rule may use, which values a field may hold, which fields\n * a pattern fits, and which fields a list of permitted fields shows.\n *\n * The catalog describes the shape of the values, not the checks of the\n * Schema. A check, such as a minimum length, only keeps some values out, so a\n * catalog that leaves the checks out holds every real value and more. An\n * answer that holds for all of these values, as `All` and `None` of a\n * coverage do, then holds for the real values too.\n */\nimport * as Arr from \"effect/Array\"\nimport * as HashMap from \"effect/HashMap\"\nimport * as HashSet from \"effect/HashSet\"\nimport * as Option from \"effect/Option\"\nimport { hasProperty } from \"effect/Predicate\"\nimport * as Schema from \"effect/Schema\"\nimport * as SchemaAST from \"effect/SchemaAST\"\nimport * as Limits from \"./limits.ts\"\nimport * as Trampoline from \"./trampoline.ts\"\n\n/** The kinds of values that a field may hold. */\nexport interface Kinds {\n\t/** The field may be `null`, `undefined` or missing. */\n\treadonly null: boolean\n\treadonly boolean: boolean\n\t/** The field may hold any string. */\n\treadonly string: boolean\n\t/** The numbers that the field may hold. */\n\treadonly number: \"none\" | \"integer\" | \"finite\" | \"any\"\n\t/** The field may hold an object, or a value that is not a scalar. */\n\treadonly object: boolean\n\t/** Single values that the field may hold, other than the ones above. */\n\treadonly literals: ReadonlyArray<string | number | boolean>\n}\n\n/** No value: the kinds of a field of `Schema.Never`. */\nexport const noKinds: Kinds = {\n\tnull: false,\n\tboolean: false,\n\tstring: false,\n\tnumber: \"none\",\n\tobject: false,\n\tliterals: [],\n}\n\n/** Any value. */\nexport const anyKinds: Kinds = {\n\tnull: true,\n\tboolean: true,\n\tstring: true,\n\tnumber: \"any\",\n\tobject: true,\n\tliterals: [],\n}\n\nconst nullKinds: Kinds = { ...noKinds, null: true }\nconst objectKinds: Kinds = { ...noKinds, object: true }\n\nconst numberOrder = [\"none\", \"integer\", \"finite\", \"any\"] as const\n\n/** The kinds of a field that may hold a value of `left` or of `right`. */\nexport const joinKinds = (left: Kinds, right: Kinds): Kinds => ({\n\tnull: left.null || right.null,\n\tboolean: left.boolean || right.boolean,\n\tstring: left.string || right.string,\n\tnumber:\n\t\tnumberOrder.indexOf(left.number) >= numberOrder.indexOf(right.number)\n\t\t\t? left.number\n\t\t\t: right.number,\n\tobject: left.object || right.object,\n\t// A native `Set` finds copies about ten times as fast as `Arr.dedupe`. It\n\t// takes `0` and `-0` as one, as `includes` does when a test reads them.\n\tliterals: Array.from(new Set([...left.literals, ...right.literals])),\n})\n\n/** Tests if a number fits the numbers of a field. */\nexport const fitsNumber = (number: Kinds[\"number\"], value: number): boolean => {\n\tswitch (number) {\n\t\tcase \"none\":\n\t\t\treturn false\n\t\tcase \"integer\":\n\t\t\treturn Number.isInteger(value)\n\t\tcase \"finite\":\n\t\t\treturn Number.isFinite(value)\n\t\tcase \"any\":\n\t\t\treturn true\n\t}\n}\n\n/**\n * Tests if a field with these kinds may hold a value. A missing value and\n * `undefined` are `null`.\n */\nexport const accepts = (kinds: Kinds, value: unknown): boolean => {\n\tif (value === null || value === undefined) {\n\t\treturn kinds.null\n\t}\n\tswitch (typeof value) {\n\t\tcase \"boolean\":\n\t\t\treturn kinds.boolean || kinds.literals.includes(value)\n\t\tcase \"string\":\n\t\t\treturn kinds.string || kinds.literals.includes(value)\n\t\tcase \"number\":\n\t\t\treturn fitsNumber(kinds.number, value) || kinds.literals.includes(value)\n\t\tdefault:\n\t\t\treturn kinds.object\n\t}\n}\n\n/** A part of a field path that stands for any key of a record. */\nexport interface Key {\n\treadonly _tag: \"Key\"\n\t/** The Schemas of the keys. A key must fit one of them. */\n\treadonly parameters: ReadonlyArray<SchemaAST.AST>\n}\n\n/** One part of a field path: a name, or any key of a record. */\nexport type Segment = string | Key\n\n/** One field of a catalog. */\nexport interface Field {\n\treadonly path: ReadonlyArray<Segment>\n\t/** The path with dots, and `*` for a key of a record. */\n\treadonly text: string\n\treadonly kinds: Kinds\n\t/**\n\t * The field holds a value that a list of fields shows as one field: a\n\t * scalar, a record, an array, or a struct at the last part a path may have.\n\t */\n\treadonly listed: boolean\n\t/** The field may hold parts that the Schema does not name, as a record may. */\n\treadonly open: boolean\n\t/**\n\t * A read of the field finds only plain data. It is `false` when a value\n\t * above the field may be an object that does not have it as a data field:\n\t * a class without the field, which may have a getter of that name, a\n\t * value such as a date or a list, or any object, as of `Schema.Unknown`.\n\t * A struct of a union without the field makes it not plain too, since a\n\t * value made by hand may have it with any value.\n\t */\n\treadonly plain: boolean\n}\n\n/**\n * A place in the graph of a catalog. A path of a field leads to a place, and\n * many paths may lead to one place, as in a recursive Schema: `next` and\n * `next.next` of a list of links are one place. A place keeps the ids of the\n * places below it, so the graph has no loop of objects.\n */\nexport interface Branch {\n\treadonly id: number\n\t/** The kinds of the values of the field at this place. */\n\treadonly kinds: Kinds\n\t/** The field holds a scalar, a record or a list: see `Field.listed`. */\n\treadonly values: boolean\n\t/** The field may hold a struct. */\n\treadonly structs: boolean\n\treadonly open: boolean\n\treadonly plain: boolean\n\t/** The ids of the places below, by name, in the order of the Schema. */\n\treadonly children: ReadonlyArray<readonly [string, number]>\n\t/**\n\t * The places below, by name. A lookup gets each name from `split`, as a\n\t * new string, and a `HashMap` finds a new string about 20% faster than a\n\t * native `Map`. The names come from the Schema, so a name from a policy\n\t * can not make a long list of names with one hash here.\n\t */\n\treadonly names: HashMap.HashMap<string, number>\n\t/** The place for the keys of a record, when the value may be a record. */\n\treadonly key: Key | undefined\n\treadonly keyBranch: number | undefined\n}\n\n/** The catalog of the fields of a Schema. */\nexport interface Catalog {\n\t/**\n\t * The fields that a list shows, a field before the fields below it. For a\n\t * recursive Schema, the list goes into each recursive place one time on\n\t * each path, so it is finite.\n\t */\n\treadonly fields: ReadonlyArray<Field>\n\t/** The places, by id. The root is the place with the id 0. */\n\treadonly branches: ReadonlyArray<Branch>\n\t/**\n\t * The number of steps from a place to a place below it. A search of the\n\t * catalog, as for a field pattern, reads each one, so this is its work.\n\t */\n\treadonly edges: number\n\treadonly root: Branch\n}\n\n/**\n * Tests if a key of an object fits the Schemas of the keys of a record. A key\n * of an object is a string, also a key that a number Schema takes: `{ 1: \"a\" }`\n * has the key `\"1\"`. So a key that is the text of a number is tried as that\n * number too.\n */\nexport const keyFits = (key: Key, name: string): boolean =>\n\tArr.some(key.parameters, (parameter) => {\n\t\tconst is = Schema.is(Schema.make(parameter))\n\t\treturn (\n\t\t\tis(name) ||\n\t\t\t(name !== \"\" && String(Number(name)) === name && is(Number(name)))\n\t\t)\n\t})\n\n/**\n * Gives the struct of a `Schema.Class`. A class is a declaration with one\n * struct as its type parameter, and Effect marks it with a `\"~constructor\"`\n * annotation, also through more codec steps, as in\n * `Schema.fromJsonString(User)`. Other declarations, such as `Schema.Option`\n * or `Schema.ReadonlyMap`, have no such mark, and the fields of their type\n * parameter are not fields of the value.\n */\nconst classStruct = (\n\tast: SchemaAST.Declaration,\n): SchemaAST.Objects | undefined => {\n\tconst [inner] = ast.typeParameters\n\treturn ast.annotations?.[\"~constructor\"] !== undefined &&\n\t\tast.typeParameters.length === 1 &&\n\t\tinner?._tag === \"Objects\"\n\t\t? inner\n\t\t: undefined\n}\n\n/**\n * Gives the Schemas that a value at one place may fit: it opens unions and\n * `Schema.suspend`. A class stays a class: see {@link structOf}. The open suspend nodes are in a native `Set`,\n * because the walk must find a node by the node itself: `Equal` compares\n * Schema nodes by structure, and a recursive Schema has no end. A suspend\n * node that gives itself again, with no step on the path, gives nothing.\n */\nconst alternatives = (\n\tast: SchemaAST.AST,\n\topen: ReadonlySet<SchemaAST.AST>,\n): ReadonlyArray<SchemaAST.AST> => alternativesOf({ ast, open })\n\n/**\n * The walk of {@link alternatives}. A union may hold a union, very many\n * levels down, so it is a walk of the trampoline for an input of an unknown\n * depth.\n */\nconst alternativesOf = Trampoline.adaptive<\n\t{ readonly ast: SchemaAST.AST; readonly open: ReadonlySet<SchemaAST.AST> },\n\tReadonlyArray<SchemaAST.AST>\n>((R, child) => ({ ast, open }, depth) => {\n\tswitch (ast._tag) {\n\t\tcase \"Union\":\n\t\t\treturn R.map(\n\t\t\t\tR.forEach(ast.types, (type) => child({ ast: type, open }, depth + 1)),\n\t\t\t\tArr.flatten,\n\t\t\t)\n\t\tcase \"Suspend\":\n\t\t\treturn open.has(ast)\n\t\t\t\t? R.succeed([])\n\t\t\t\t: child({ ast: ast.thunk(), open: new Set([...open, ast]) }, depth + 1)\n\t\tdefault:\n\t\t\treturn R.succeed([ast])\n\t}\n})\n\n/** The struct of a Schema: a struct, or the struct of a class. */\nconst structOf = (ast: SchemaAST.AST): SchemaAST.Objects | undefined =>\n\tast._tag === \"Objects\"\n\t\t? ast\n\t\t: ast._tag === \"Declaration\"\n\t\t\t? classStruct(ast)\n\t\t\t: undefined\n\nconst hasCheck = (\n\tchecks: ReadonlyArray<SchemaAST.Check<never>>,\n\tid: string,\n): boolean =>\n\tArr.some(\n\t\tchecks,\n\t\t(check) =>\n\t\t\tcheck.annotations?.representation?.id === id ||\n\t\t\t(check._tag === \"FilterGroup\" && hasCheck(check.checks, id)),\n\t)\n\n/** A struct with named fields, or with the keys of a record. */\nconst isStruct = (\n\tast: SchemaAST.Objects | undefined,\n): ast is SchemaAST.Objects =>\n\tast !== undefined &&\n\t(ast.propertySignatures.length > 0 || ast.indexSignatures.length > 0)\n\n/** The kinds of the values of a Schema that is not a struct. */\nconst kindsOf = (ast: SchemaAST.AST): Kinds => {\n\tswitch (ast._tag) {\n\t\tcase \"Null\":\n\t\tcase \"Undefined\":\n\t\t\treturn nullKinds\n\t\tcase \"Never\":\n\t\t\treturn noKinds\n\t\tcase \"String\":\n\t\tcase \"TemplateLiteral\":\n\t\t\treturn { ...noKinds, string: true }\n\t\tcase \"Boolean\":\n\t\t\treturn { ...noKinds, boolean: true }\n\t\tcase \"Number\": {\n\t\t\t// The two checks that a search uses. Effect names a number with\n\t\t\t// them, as `Schema.Int` and `Schema.Finite`; other checks are left\n\t\t\t// out, as all checks are.\n\t\t\tconst checks = ast.checks ?? []\n\t\t\treturn {\n\t\t\t\t...noKinds,\n\t\t\t\tnumber: hasCheck(checks, \"effect/schema/isInt\")\n\t\t\t\t\t? \"integer\"\n\t\t\t\t\t: hasCheck(checks, \"effect/schema/isFinite\")\n\t\t\t\t\t\t? \"finite\"\n\t\t\t\t\t\t: \"any\",\n\t\t\t}\n\t\t}\n\t\tcase \"Literal\":\n\t\t\treturn typeof ast.literal === \"bigint\"\n\t\t\t\t? objectKinds\n\t\t\t\t: { ...noKinds, literals: [ast.literal] }\n\t\tcase \"Enum\":\n\t\t\treturn { ...noKinds, literals: Arr.map(ast.enums, ([, value]) => value) }\n\t\tcase \"Arrays\":\n\t\tcase \"ObjectKeyword\":\n\t\tcase \"BigInt\":\n\t\tcase \"Symbol\":\n\t\tcase \"UniqueSymbol\":\n\t\t\treturn objectKinds\n\t\tcase \"Declaration\":\n\t\t\t// A declaration of Effect in `objectDeclarations` holds an object,\n\t\t\t// such as a `Date`. Another declaration may hold any value, as\n\t\t\t// `Schema.Json` or one from `Schema.declare` may.\n\t\t\treturn HashSet.has(objectDeclarations, declarationId(ast))\n\t\t\t\t? objectKinds\n\t\t\t\t: anyKinds\n\t\tdefault:\n\t\t\t// `Schema.Unknown`, `Schema.Void`, and `Struct({})`, which takes a\n\t\t\t// string too: these may hold any value.\n\t\t\treturn anyKinds\n\t}\n}\n\n/**\n * The ids of the declarations of Effect that hold only objects. Effect puts\n * the id in the `representation` annotation. A declaration that is not in\n * this list may hold a scalar, as `Schema.Json` may, so its kinds are any\n * value.\n */\nconst objectDeclarations = HashSet.make(\n\t\"effect/schema/Date\",\n\t\"effect/schema/URL\",\n\t\"effect/schema/Uint8Array\",\n\t\"effect/schema/Duration\",\n\t\"effect/schema/DateTimeUtc\",\n\t\"effect/schema/DateTimeZoned\",\n\t\"effect/schema/BigDecimal\",\n\t\"effect/schema/Redacted\",\n\t\"effect/schema/Cause\",\n\t\"effect/schema/Chunk\",\n\t\"effect/schema/HashMap\",\n\t\"effect/schema/HashSet\",\n\t\"effect/schema/ReadonlyMap\",\n\t\"effect/schema/ReadonlySet\",\n\t\"effect/schema/Option\",\n\t\"effect/schema/Result\",\n\t\"effect/schema/Exit\",\n\t\"effect/schema/File\",\n\t\"effect/schema/FormData\",\n\t\"effect/schema/RegExp\",\n\t\"effect/schema/URLSearchParams\",\n)\n\nconst declarationId = (ast: SchemaAST.Declaration): string | undefined => {\n\tconst representation: unknown = ast.annotations?.[\"representation\"]\n\treturn hasProperty(representation, \"id\") &&\n\t\ttypeof representation.id === \"string\"\n\t\t? representation.id\n\t\t: undefined\n}\n\n/** The things that the walk of a catalog knows at one place. */\ninterface Place {\n\treadonly schemas: ReadonlyArray<SchemaAST.AST>\n\t/** The value may be missing. */\n\treadonly missing: boolean\n\t/** The value may be anything, as an extra field of a struct may be. */\n\treadonly top: boolean\n\t/** A read here finds only plain data: see {@link Field.plain}. */\n\treadonly plain: boolean\n}\n\nconst textOf = (path: ReadonlyArray<Segment>): string =>\n\tArr.map(path, (segment) =>\n\t\ttypeof segment === \"string\" ? segment : \"*\",\n\t).join(\".\")\n\n/**\n * The name of a property as a part of a path. A key of an object is a string,\n * so a number key, as from `Schema.Record(Schema.Literal(1), ...)`, has the\n * name `\"1\"`. A symbol key is not a field.\n */\nconst nameOf = (signature: SchemaAST.PropertySignature): string | undefined =>\n\ttypeof signature.name === \"symbol\" ? undefined : String(signature.name)\n\n/**\n * The properties of a struct by name. A walk makes it one time for each\n * struct of a place, so a struct with very many fields does not take a\n * search for each name. It is a native `Map`, as `Branch.names` is.\n */\nconst propertiesOf = (\n\tstruct: SchemaAST.Objects,\n): ReadonlyMap<string, SchemaAST.PropertySignature> =>\n\tnew Map(\n\t\tArr.flatMap(struct.propertySignatures, (signature) => {\n\t\t\tconst name = nameOf(signature)\n\t\t\treturn name === undefined ? [] : [[name, signature] as const]\n\t\t}),\n\t)\n\n/** The names of the fields of the structs, in the order that they first come. */\nconst namesOf = (\n\tstructs: ReadonlyArray<SchemaAST.Objects>,\n): ReadonlyArray<string> =>\n\t// A native `Set` keeps the first copy of each name, in order, about ten\n\t// times as fast as `Arr.dedupe`.\n\tArray.from(\n\t\tnew Set(\n\t\t\tArr.flatMap(structs, (struct) =>\n\t\t\t\tArr.flatMap(struct.propertySignatures, (signature) => {\n\t\t\t\t\tconst name = nameOf(signature)\n\t\t\t\t\tif (name === undefined) {\n\t\t\t\t\t\treturn []\n\t\t\t\t\t}\n\t\t\t\t\t// A dot in a path is the step into a nested field. A field name with\n\t\t\t\t\t// a dot would make two fields have one path, so it is a defect.\n\t\t\t\t\tif (name.includes(\".\")) {\n\t\t\t\t\t\tthrow new Error(`A field name may not have a dot: \"${name}\"`)\n\t\t\t\t\t}\n\t\t\t\t\t// An empty name would make an empty part in a path, such as\n\t\t\t\t\t// `.secret`.\n\t\t\t\t\tif (name === \"\") {\n\t\t\t\t\t\tthrow new Error(\"A field name may not be empty\")\n\t\t\t\t\t}\n\t\t\t\t\treturn [name]\n\t\t\t\t}),\n\t\t\t),\n\t\t),\n\t)\n\n/** A place of the walk, and the places below it, before they have ids. */\ninterface Made {\n\treadonly branch: Omit<Branch, \"id\" | \"children\" | \"names\" | \"keyBranch\">\n\treadonly children: ReadonlyArray<readonly [string, Place]>\n\treadonly keyPlace: Place | undefined\n}\n\n/** Works out the facts of a place, and the places below it. */\nconst placeOf = (place: Place): Made => {\n\tconst structs = Arr.filter(Arr.map(place.schemas, structOf), isStruct)\n\tconst values = Arr.filter(\n\t\tplace.schemas,\n\t\t(schema) => !isStruct(structOf(schema)),\n\t)\n\tconst valueKinds = Arr.reduce(values, noKinds, (kinds, schema) =>\n\t\tjoinKinds(kinds, kindsOf(structOf(schema) ?? schema)),\n\t)\n\tconst kinds = place.top\n\t\t? anyKinds\n\t\t: joinKinds(\n\t\t\t\tjoinKinds(valueKinds, structs.length > 0 ? objectKinds : noKinds),\n\t\t\t\tplace.missing ? nullKinds : noKinds,\n\t\t\t)\n\tconst records = Arr.filter(\n\t\tstructs,\n\t\t(struct) => struct.indexSignatures.length > 0,\n\t)\n\tconst structProperties = Arr.map(structs, propertiesOf)\n\tconst children = Arr.map(namesOf(structs), (name) => {\n\t\tconst properties = Arr.flatMap(structProperties, (named) => {\n\t\t\tconst property = named.get(name)\n\t\t\treturn property === undefined ? [] : [property]\n\t\t})\n\t\t// A struct without the name may still have it as an extra field, with\n\t\t// any value: only a decode or a `make` takes extra fields away, and a\n\t\t// decision may get a value made by hand. A value that may be any\n\t\t// object, as one of `Schema.Unknown` may, may have the name with any\n\t\t// value too.\n\t\tconst top =\n\t\t\tplace.top || valueKinds.object || properties.length < structs.length\n\t\tconst below: Place = {\n\t\t\tschemas: Arr.flatMap(properties, (property) =>\n\t\t\t\talternatives(property.type, new Set()),\n\t\t\t),\n\t\t\t// A field below a value that is not a struct reads `null`, and so\n\t\t\t// does a field that may be missing.\n\t\t\tmissing:\n\t\t\t\tplace.missing ||\n\t\t\t\tvalues.length > 0 ||\n\t\t\t\tArr.some(properties, (property) => SchemaAST.isOptional(property.type)),\n\t\t\ttop,\n\t\t\t// A value that may be anything may be a list, or have a getter, so\n\t\t\t// a read below it may not find plain data.\n\t\t\tplain: place.plain && !top,\n\t\t}\n\t\treturn [name, below] as const\n\t})\n\tconst signatures = Arr.flatMap(records, (struct) => struct.indexSignatures)\n\tconst keyTop =\n\t\tplace.top || valueKinds.object || records.length < structs.length\n\treturn {\n\t\tbranch: {\n\t\t\tkinds,\n\t\t\tvalues: values.length > 0 || records.length > 0,\n\t\t\tstructs: structs.length > 0,\n\t\t\t// A field that is a scalar in one branch of a union and a struct in\n\t\t\t// another holds parts too.\n\t\t\topen:\n\t\t\t\trecords.length > 0 ||\n\t\t\t\tstructs.length > 0 ||\n\t\t\t\tvalueKinds.object ||\n\t\t\t\tplace.top,\n\t\t\tplain: place.plain,\n\t\t\tkey:\n\t\t\t\tsignatures.length === 0\n\t\t\t\t\t? undefined\n\t\t\t\t\t: {\n\t\t\t\t\t\t\t_tag: \"Key\",\n\t\t\t\t\t\t\tparameters: Arr.map(\n\t\t\t\t\t\t\t\tsignatures,\n\t\t\t\t\t\t\t\t(signature) => signature.parameter,\n\t\t\t\t\t\t\t),\n\t\t\t\t\t\t},\n\t\t},\n\t\tchildren,\n\t\tkeyPlace:\n\t\t\tsignatures.length === 0\n\t\t\t\t? undefined\n\t\t\t\t: {\n\t\t\t\t\t\tschemas: Arr.flatMap(signatures, (signature) =>\n\t\t\t\t\t\t\talternatives(signature.type, new Set()),\n\t\t\t\t\t\t),\n\t\t\t\t\t\t// A record may not have a given key.\n\t\t\t\t\t\tmissing: true,\n\t\t\t\t\t\ttop: keyTop,\n\t\t\t\t\t\tplain: place.plain && !keyTop,\n\t\t\t\t\t},\n\t}\n}\n\n/**\n * Makes the places of a Schema. Each place comes one time: two paths with the\n * same Schemas and the same marks lead to one place. The walk keeps a queue\n * of the places that it has found, and it makes them in order, so a deep\n * Schema does not fill the stack.\n */\nconst branchesOf = (root: Place): ReadonlyArray<Branch> => {\n\t// The id of each Schema node, and of each place, for this walk. The ids\n\t// are in native maps, because the walk must find a Schema node by the node\n\t// itself: `Equal` compares Schema nodes by structure, and a recursive\n\t// Schema has no end. The maps and the queue belong to this walk.\n\tconst astIds = new Map<SchemaAST.AST, number>()\n\tconst placeIds = new Map<string, number>()\n\tconst queue: Array<Place> = []\n\tconst idOf = (place: Place): number => {\n\t\tconst ids = Arr.map(place.schemas, (schema) => {\n\t\t\tconst known = astIds.get(schema)\n\t\t\tif (known !== undefined) {\n\t\t\t\treturn known\n\t\t\t}\n\t\t\tastIds.set(schema, astIds.size)\n\t\t\treturn astIds.size - 1\n\t\t})\n\t\tconst key = `${Array.from(new Set(ids))\n\t\t\t.sort((a, b) => a - b)\n\t\t\t.join(\n\t\t\t\t\",\",\n\t\t\t)}|${String(place.missing)}|${String(place.top)}|${String(place.plain)}`\n\t\tconst known = placeIds.get(key)\n\t\tif (known !== undefined) {\n\t\t\treturn known\n\t\t}\n\t\tif (queue.length >= Limits.ceilings.catalogFields) {\n\t\t\tthrow Limits.exceeded(\n\t\t\t\t\"A Schema\",\n\t\t\t\t\"catalogFields\",\n\t\t\t\t\"places\",\n\t\t\t\tqueue.length + 1,\n\t\t\t\tLimits.ceilings.catalogFields,\n\t\t\t)\n\t\t}\n\t\tplaceIds.set(key, queue.length)\n\t\tqueue.push(place)\n\t\treturn queue.length - 1\n\t}\n\tidOf(root)\n\treturn Arr.unfold(0, (id) => {\n\t\tconst place = queue[id]\n\t\tif (place === undefined) {\n\t\t\treturn Option.none()\n\t\t}\n\t\tconst made = placeOf(place)\n\t\tconst children = Arr.map(\n\t\t\tmade.children,\n\t\t\t([name, below]) => [name, idOf(below)] as const,\n\t\t)\n\t\tconst branch: Branch = {\n\t\t\t...made.branch,\n\t\t\tid,\n\t\t\tchildren,\n\t\t\tnames: HashMap.fromIterable(children),\n\t\t\tkeyBranch: made.keyPlace === undefined ? undefined : idOf(made.keyPlace),\n\t\t}\n\t\treturn Option.some([branch, id + 1] as const)\n\t})\n}\n\n/** The field at a place, for a path. */\nconst fieldAt = (\n\tbranch: Branch,\n\tpath: ReadonlyArray<Segment>,\n\tparts: number,\n): Field => ({\n\tpath,\n\ttext: textOf(path),\n\tkinds: branch.kinds,\n\t// A struct at the last part that a path may have is one field, since a\n\t// path does not go below it.\n\tlisted: branch.values || (path.length === parts && branch.structs),\n\topen: branch.open,\n\tplain: branch.plain,\n})\n\n/**\n * The fields that a list shows, a field before the fields below it, down to\n * `parts` parts. A path goes into a place that is on it already one time\n * only, so the list of a recursive Schema is finite. The walk keeps a stack\n * of the places to visit, so a deep Schema does not fill the stack. The\n * stack belongs to this walk.\n */\nconst listOf = (\n\tbranches: ReadonlyArray<Branch>,\n\tparts: number,\n): ReadonlyArray<Field> => {\n\tinterface Visit {\n\t\treadonly branch: Branch\n\t\treadonly path: ReadonlyArray<Segment>\n\t\t/** The ids of the places on the path, as a set of Effect that shares its parts. */\n\t\treadonly above: HashSet.HashSet<number>\n\t}\n\tconst below = (visit: Visit): ReadonlyArray<Visit> => {\n\t\tconst { branch, path } = visit\n\t\tif (\n\t\t\tpath.length === parts ||\n\t\t\t(path.length > 0 && HashSet.has(visit.above, branch.id))\n\t\t) {\n\t\t\treturn []\n\t\t}\n\t\tconst above = HashSet.add(visit.above, branch.id)\n\t\tconst named = Arr.map(branch.children, ([name, id]) => ({\n\t\t\tbranch: branches[id]!,\n\t\t\tpath: [...path, name],\n\t\t\tabove,\n\t\t}))\n\t\treturn branch.key === undefined || branch.keyBranch === undefined\n\t\t\t? named\n\t\t\t: [\n\t\t\t\t\t...named,\n\t\t\t\t\t{\n\t\t\t\t\t\tbranch: branches[branch.keyBranch]!,\n\t\t\t\t\t\tpath: [...path, branch.key],\n\t\t\t\t\t\tabove,\n\t\t\t\t\t},\n\t\t\t\t]\n\t}\n\tconst stack: Array<Visit> = [\n\t\t{ branch: branches[0]!, path: [], above: HashSet.empty() },\n\t]\n\treturn Arr.flatten(\n\t\tArr.unfold(stack, (rest) => {\n\t\t\tconst visit = rest.pop()\n\t\t\tif (visit === undefined) {\n\t\t\t\treturn Option.none()\n\t\t\t}\n\t\t\t// The visits go onto the stack one at a time: a spread into a call\n\t\t\t// would pass one argument for each field, past the most arguments\n\t\t\t// of a call.\n\t\t\tArr.forEach(Arr.reverse(below(visit)), (next) => {\n\t\t\t\trest.push(next)\n\t\t\t})\n\t\t\tconst field =\n\t\t\t\tvisit.path.length === 0\n\t\t\t\t\t? []\n\t\t\t\t\t: [fieldAt(visit.branch, visit.path, parts)]\n\t\t\tif (field.length > 0 && rest.length + 1 > Limits.ceilings.catalogFields) {\n\t\t\t\tthrow Limits.exceeded(\n\t\t\t\t\t\"A Schema\",\n\t\t\t\t\t\"catalogFields\",\n\t\t\t\t\t\"fields\",\n\t\t\t\t\trest.length + 1,\n\t\t\t\t\tLimits.ceilings.catalogFields,\n\t\t\t\t)\n\t\t\t}\n\t\t\treturn Option.some([field, rest] as const)\n\t\t}),\n\t)\n}\n\n/**\n * The fields that a list shows for paths of at most `parts` parts, as for a\n * domain with that limit. See `Catalog.fields`.\n */\nexport const fieldsOf = (\n\tcatalog: Catalog,\n\tparts: number,\n): ReadonlyArray<Field> =>\n\tparts === Limits.fieldPathParts\n\t\t? catalog.fields\n\t\t: listOf(catalog.branches, parts)\n\n/**\n * Makes the catalog of a Schema, with the list of its fields for the default\n * limit of the parts of a path. It throws when a field name has a dot or is\n * empty, and when the Schema has more places or fields than the ceiling of\n * `catalogFields`. A domain checks the size of the list with its own limit.\n */\nexport const catalogOf = (ast: SchemaAST.AST): Catalog => {\n\tconst branches = branchesOf({\n\t\tschemas: alternatives(ast, new Set()),\n\t\tmissing: false,\n\t\ttop: false,\n\t\tplain: true,\n\t})\n\treturn {\n\t\tfields: listOf(branches, Limits.fieldPathParts),\n\t\tbranches,\n\t\tedges: Arr.reduce(\n\t\t\tbranches,\n\t\t\t0,\n\t\t\t(total, branch) =>\n\t\t\t\ttotal +\n\t\t\t\tbranch.children.length +\n\t\t\t\t(branch.keyBranch === undefined ? 0 : 1),\n\t\t),\n\t\troot: branches[0]!,\n\t}\n}\n\n/**\n * Gives the field of a catalog at a path with dots, if the catalog has it.\n * A path has at most `parts` parts. The lookup follows one place for each\n * part, in a loop.\n */\nexport const lookup = (\n\tcatalog: Catalog,\n\tpath: string,\n\tparts: number = Limits.fieldPathParts,\n): Field | undefined => {\n\tconst names = path.split(\".\", parts + 1)\n\tif (names.length > parts) {\n\t\treturn undefined\n\t}\n\tconst found = Arr.reduce(\n\t\tnames,\n\t\t{ branch: catalog.root, path: [] } as\n\t\t\t| { readonly branch: Branch; readonly path: ReadonlyArray<Segment> }\n\t\t\t| undefined,\n\t\t(current, name) => {\n\t\t\tif (current === undefined) {\n\t\t\t\treturn undefined\n\t\t\t}\n\t\t\tconst named = HashMap.get(current.branch.names, name)\n\t\t\tif (Option.isSome(named)) {\n\t\t\t\treturn {\n\t\t\t\t\tbranch: catalog.branches[named.value]!,\n\t\t\t\t\tpath: [...current.path, name],\n\t\t\t\t}\n\t\t\t}\n\t\t\tconst key = current.branch.key\n\t\t\tconst keyBranch = current.branch.keyBranch\n\t\t\treturn key !== undefined && keyBranch !== undefined && keyFits(key, name)\n\t\t\t\t? { branch: catalog.branches[keyBranch]!, path: [...current.path, key] }\n\t\t\t\t: undefined\n\t\t},\n\t)\n\treturn found === undefined\n\t\t? undefined\n\t\t: fieldAt(found.branch, found.path, parts)\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;AAsCA,MAAa,UAAiB;CAC7B,MAAM;CACN,SAAS;CACT,QAAQ;CACR,QAAQ;CACR,QAAQ;CACR,UAAU,CAAC;AACZ;;AAGA,MAAa,WAAkB;CAC9B,MAAM;CACN,SAAS;CACT,QAAQ;CACR,QAAQ;CACR,QAAQ;CACR,UAAU,CAAC;AACZ;AAEA,MAAM,YAAmB;CAAE,GAAG;CAAS,MAAM;AAAK;AAClD,MAAM,cAAqB;CAAE,GAAG;CAAS,QAAQ;AAAK;AAEtD,MAAM,cAAc;CAAC;CAAQ;CAAW;CAAU;AAAK;;AAGvD,MAAa,aAAa,MAAa,WAAyB;CAC/D,MAAM,KAAK,QAAQ,MAAM;CACzB,SAAS,KAAK,WAAW,MAAM;CAC/B,QAAQ,KAAK,UAAU,MAAM;CAC7B,QACC,YAAY,QAAQ,KAAK,MAAM,KAAK,YAAY,QAAQ,MAAM,MAAM,IACjE,KAAK,SACL,MAAM;CACV,QAAQ,KAAK,UAAU,MAAM;CAG7B,UAAU,MAAM,qBAAK,IAAI,IAAI,CAAC,GAAG,KAAK,UAAU,GAAG,MAAM,QAAQ,CAAC,CAAC;AACpE;;AAGA,MAAa,cAAc,QAAyB,UAA2B;CAC9E,QAAQ,QAAR;EACC,KAAK,QACJ,OAAO;EACR,KAAK,WACJ,OAAO,OAAO,UAAU,KAAK;EAC9B,KAAK,UACJ,OAAO,OAAO,SAAS,KAAK;EAC7B,KAAK,OACJ,OAAO;CACT;AACD;;;;;AAMA,MAAa,WAAW,OAAc,UAA4B;CACjE,IAAI,UAAU,QAAQ,UAAU,KAAA,GAC/B,OAAO,MAAM;CAEd,QAAQ,OAAO,OAAf;EACC,KAAK,WACJ,OAAO,MAAM,WAAW,MAAM,SAAS,SAAS,KAAK;EACtD,KAAK,UACJ,OAAO,MAAM,UAAU,MAAM,SAAS,SAAS,KAAK;EACrD,KAAK,UACJ,OAAO,WAAW,MAAM,QAAQ,KAAK,KAAK,MAAM,SAAS,SAAS,KAAK;EACxE,SACC,OAAO,MAAM;CACf;AACD;;;;;;;AA0FA,MAAa,WAAW,KAAU,SACjC,IAAI,KAAK,IAAI,aAAa,cAAc;CACvC,MAAM,KAAK,OAAO,GAAG,OAAO,KAAK,SAAS,CAAC;CAC3C,OACC,GAAG,IAAI,KACN,SAAS,MAAM,OAAO,OAAO,IAAI,CAAC,MAAM,QAAQ,GAAG,OAAO,IAAI,CAAC;AAElE,CAAC;;;;;;;;;AAUF,MAAM,eACL,QACmC;CACnC,MAAM,CAAC,SAAS,IAAI;CACpB,OAAO,IAAI,cAAc,oBAAoB,KAAA,KAC5C,IAAI,eAAe,WAAW,KAC9B,OAAO,SAAS,YACd,QACA,KAAA;AACJ;;;;;;;;AASA,MAAM,gBACL,KACA,SACkC,eAAe;CAAE;CAAK;AAAK,CAAC;;;;;;AAO/D,MAAM,iBAAiBA,UAGpB,GAAG,WAAW,EAAE,KAAK,QAAQ,UAAU;CACzC,QAAQ,IAAI,MAAZ;EACC,KAAK,SACJ,OAAO,EAAE,IACR,EAAE,QAAQ,IAAI,QAAQ,SAAS,MAAM;GAAE,KAAK;GAAM;EAAK,GAAG,QAAQ,CAAC,CAAC,GACpE,IAAI,OACL;EACD,KAAK,WACJ,OAAO,KAAK,IAAI,GAAG,IAChB,EAAE,QAAQ,CAAC,CAAC,IACZ,MAAM;GAAE,KAAK,IAAI,MAAM;GAAG,sBAAM,IAAI,IAAI,CAAC,GAAG,MAAM,GAAG,CAAC;EAAE,GAAG,QAAQ,CAAC;EACxE,SACC,OAAO,EAAE,QAAQ,CAAC,GAAG,CAAC;CACxB;AACD,CAAC;;AAGD,MAAM,YAAY,QACjB,IAAI,SAAS,YACV,MACA,IAAI,SAAS,gBACZ,YAAY,GAAG,IACf,KAAA;AAEL,MAAM,YACL,QACA,OAEA,IAAI,KACH,SACC,UACA,MAAM,aAAa,gBAAgB,OAAO,MACzC,MAAM,SAAS,iBAAiB,SAAS,MAAM,QAAQ,EAAE,CAC5D;;AAGD,MAAM,YACL,QAEA,QAAQ,KAAA,MACP,IAAI,mBAAmB,SAAS,KAAK,IAAI,gBAAgB,SAAS;;AAGpE,MAAM,WAAW,QAA8B;CAC9C,QAAQ,IAAI,MAAZ;EACC,KAAK;EACL,KAAK,aACJ,OAAO;EACR,KAAK,SACJ,OAAO;EACR,KAAK;EACL,KAAK,mBACJ,OAAO;GAAE,GAAG;GAAS,QAAQ;EAAK;EACnC,KAAK,WACJ,OAAO;GAAE,GAAG;GAAS,SAAS;EAAK;EACpC,KAAK,UAAU;GAId,MAAM,SAAS,IAAI,UAAU,CAAC;GAC9B,OAAO;IACN,GAAG;IACH,QAAQ,SAAS,QAAQ,qBAAqB,IAC3C,YACA,SAAS,QAAQ,wBAAwB,IACxC,WACA;GACL;EACD;EACA,KAAK,WACJ,OAAO,OAAO,IAAI,YAAY,WAC3B,cACA;GAAE,GAAG;GAAS,UAAU,CAAC,IAAI,OAAO;EAAE;EAC1C,KAAK,QACJ,OAAO;GAAE,GAAG;GAAS,UAAU,IAAI,IAAI,IAAI,QAAQ,GAAG,WAAW,KAAK;EAAE;EACzE,KAAK;EACL,KAAK;EACL,KAAK;EACL,KAAK;EACL,KAAK,gBACJ,OAAO;EACR,KAAK,eAIJ,OAAO,QAAQ,IAAI,oBAAoB,cAAc,GAAG,CAAC,IACtD,cACA;EACJ,SAGC,OAAO;CACT;AACD;;;;;;;AAQA,MAAM,qBAAqB,QAAQ,KAClC,sBACA,qBACA,4BACA,0BACA,6BACA,+BACA,4BACA,0BACA,uBACA,uBACA,yBACA,yBACA,6BACA,6BACA,wBACA,wBACA,sBACA,sBACA,0BACA,wBACA,+BACD;AAEA,MAAM,iBAAiB,QAAmD;CACzE,MAAM,iBAA0B,IAAI,cAAc;CAClD,OAAO,YAAY,gBAAgB,IAAI,KACtC,OAAO,eAAe,OAAO,WAC3B,eAAe,KACf,KAAA;AACJ;AAaA,MAAM,UAAU,SACf,IAAI,IAAI,OAAO,YACd,OAAO,YAAY,WAAW,UAAU,GACzC,CAAC,CAAC,KAAK,GAAG;;;;;;AAOX,MAAM,UAAU,cACf,OAAO,UAAU,SAAS,WAAW,KAAA,IAAY,OAAO,UAAU,IAAI;;;;;;AAOvE,MAAM,gBACL,WAEA,IAAI,IACH,IAAI,QAAQ,OAAO,qBAAqB,cAAc;CACrD,MAAM,OAAO,OAAO,SAAS;CAC7B,OAAO,SAAS,KAAA,IAAY,CAAC,IAAI,CAAC,CAAC,MAAM,SAAS,CAAU;AAC7D,CAAC,CACF;;AAGD,MAAM,WACL,YAIA,MAAM,KACL,IAAI,IACH,IAAI,QAAQ,UAAU,WACrB,IAAI,QAAQ,OAAO,qBAAqB,cAAc;CACrD,MAAM,OAAO,OAAO,SAAS;CAC7B,IAAI,SAAS,KAAA,GACZ,OAAO,CAAC;CAIT,IAAI,KAAK,SAAS,GAAG,GACpB,MAAM,IAAI,MAAM,qCAAqC,KAAK,EAAE;CAI7D,IAAI,SAAS,IACZ,MAAM,IAAI,MAAM,+BAA+B;CAEhD,OAAO,CAAC,IAAI;AACb,CAAC,CACF,CACD,CACD;;AAUD,MAAM,WAAW,UAAuB;CACvC,MAAM,UAAU,IAAI,OAAO,IAAI,IAAI,MAAM,SAAS,QAAQ,GAAG,QAAQ;CACrE,MAAM,SAAS,IAAI,OAClB,MAAM,UACL,WAAW,CAAC,SAAS,SAAS,MAAM,CAAC,CACvC;CACA,MAAM,aAAa,IAAI,OAAO,QAAQ,UAAU,OAAO,WACtD,UAAU,OAAO,QAAQ,SAAS,MAAM,KAAK,MAAM,CAAC,CACrD;CACA,MAAM,QAAQ,MAAM,MACjB,WACA,UACA,UAAU,YAAY,QAAQ,SAAS,IAAI,cAAc,OAAO,GAChE,MAAM,UAAU,YAAY,OAC7B;CACF,MAAM,UAAU,IAAI,OACnB,UACC,WAAW,OAAO,gBAAgB,SAAS,CAC7C;CACA,MAAM,mBAAmB,IAAI,IAAI,SAAS,YAAY;CACtD,MAAM,WAAW,IAAI,IAAI,QAAQ,OAAO,IAAI,SAAS;EACpD,MAAM,aAAa,IAAI,QAAQ,mBAAmB,UAAU;GAC3D,MAAM,WAAW,MAAM,IAAI,IAAI;GAC/B,OAAO,aAAa,KAAA,IAAY,CAAC,IAAI,CAAC,QAAQ;EAC/C,CAAC;EAMD,MAAM,MACL,MAAM,OAAO,WAAW,UAAU,WAAW,SAAS,QAAQ;EAgB/D,OAAO,CAAC,MAAM;GAdb,SAAS,IAAI,QAAQ,aAAa,aACjC,aAAa,SAAS,sBAAM,IAAI,IAAI,CAAC,CACtC;GAGA,SACC,MAAM,WACN,OAAO,SAAS,KAChB,IAAI,KAAK,aAAa,aAAa,UAAU,WAAW,SAAS,IAAI,CAAC;GACvE;GAGA,OAAO,MAAM,SAAS,CAAC;EAEN,CAAC;CACpB,CAAC;CACD,MAAM,aAAa,IAAI,QAAQ,UAAU,WAAW,OAAO,eAAe;CAC1E,MAAM,SACL,MAAM,OAAO,WAAW,UAAU,QAAQ,SAAS,QAAQ;CAC5D,OAAO;EACN,QAAQ;GACP;GACA,QAAQ,OAAO,SAAS,KAAK,QAAQ,SAAS;GAC9C,SAAS,QAAQ,SAAS;GAG1B,MACC,QAAQ,SAAS,KACjB,QAAQ,SAAS,KACjB,WAAW,UACX,MAAM;GACP,OAAO,MAAM;GACb,KACC,WAAW,WAAW,IACnB,KAAA,IACA;IACA,MAAM;IACN,YAAY,IAAI,IACf,aACC,cAAc,UAAU,SAC1B;GACD;EACJ;EACA;EACA,UACC,WAAW,WAAW,IACnB,KAAA,IACA;GACA,SAAS,IAAI,QAAQ,aAAa,cACjC,aAAa,UAAU,sBAAM,IAAI,IAAI,CAAC,CACvC;GAEA,SAAS;GACT,KAAK;GACL,OAAO,MAAM,SAAS,CAAC;EACxB;CACJ;AACD;;;;;;;AAQA,MAAM,cAAc,SAAuC;CAK1D,MAAM,yBAAS,IAAI,IAA2B;CAC9C,MAAM,2BAAW,IAAI,IAAoB;CACzC,MAAM,QAAsB,CAAC;CAC7B,MAAM,QAAQ,UAAyB;EACtC,MAAM,MAAM,IAAI,IAAI,MAAM,UAAU,WAAW;GAC9C,MAAM,QAAQ,OAAO,IAAI,MAAM;GAC/B,IAAI,UAAU,KAAA,GACb,OAAO;GAER,OAAO,IAAI,QAAQ,OAAO,IAAI;GAC9B,OAAO,OAAO,OAAO;EACtB,CAAC;EACD,MAAM,MAAM,GAAG,MAAM,KAAK,IAAI,IAAI,GAAG,CAAC,CAAC,CACrC,MAAM,GAAG,MAAM,IAAI,CAAC,CAAC,CACrB,KACA,GACD,EAAE,GAAG,OAAO,MAAM,OAAO,EAAE,GAAG,OAAO,MAAM,GAAG,EAAE,GAAG,OAAO,MAAM,KAAK;EACtE,MAAM,QAAQ,SAAS,IAAI,GAAG;EAC9B,IAAI,UAAU,KAAA,GACb,OAAO;EAER,IAAI,MAAM,UAAA,SAA0B,eACnC,MAAMC,SACL,YACA,iBACA,UACA,MAAM,SAAS,GAAA,SACC,aACjB;EAED,SAAS,IAAI,KAAK,MAAM,MAAM;EAC9B,MAAM,KAAK,KAAK;EAChB,OAAO,MAAM,SAAS;CACvB;CACA,KAAK,IAAI;CACT,OAAO,IAAI,OAAO,IAAI,OAAO;EAC5B,MAAM,QAAQ,MAAM;EACpB,IAAI,UAAU,KAAA,GACb,OAAO,OAAO,KAAK;EAEpB,MAAM,OAAO,QAAQ,KAAK;EAC1B,MAAM,WAAW,IAAI,IACpB,KAAK,WACJ,CAAC,MAAM,WAAW,CAAC,MAAM,KAAK,KAAK,CAAC,CACtC;EACA,MAAM,SAAiB;GACtB,GAAG,KAAK;GACR;GACA;GACA,OAAO,QAAQ,aAAa,QAAQ;GACpC,WAAW,KAAK,aAAa,KAAA,IAAY,KAAA,IAAY,KAAK,KAAK,QAAQ;EACxE;EACA,OAAO,OAAO,KAAK,CAAC,QAAQ,KAAK,CAAC,CAAU;CAC7C,CAAC;AACF;;AAGA,MAAM,WACL,QACA,MACA,WACY;CACZ;CACA,MAAM,OAAO,IAAI;CACjB,OAAO,OAAO;CAGd,QAAQ,OAAO,UAAW,KAAK,WAAW,SAAS,OAAO;CAC1D,MAAM,OAAO;CACb,OAAO,OAAO;AACf;;;;;;;;AASA,MAAM,UACL,UACA,UAC0B;CAO1B,MAAM,SAAS,UAAuC;EACrD,MAAM,EAAE,QAAQ,SAAS;EACzB,IACC,KAAK,WAAW,SACf,KAAK,SAAS,KAAK,QAAQ,IAAI,MAAM,OAAO,OAAO,EAAE,GAEtD,OAAO,CAAC;EAET,MAAM,QAAQ,QAAQ,IAAI,MAAM,OAAO,OAAO,EAAE;EAChD,MAAM,QAAQ,IAAI,IAAI,OAAO,WAAW,CAAC,MAAM,SAAS;GACvD,QAAQ,SAAS;GACjB,MAAM,CAAC,GAAG,MAAM,IAAI;GACpB;EACD,EAAE;EACF,OAAO,OAAO,QAAQ,KAAA,KAAa,OAAO,cAAc,KAAA,IACrD,QACA,CACA,GAAG,OACH;GACC,QAAQ,SAAS,OAAO;GACxB,MAAM,CAAC,GAAG,MAAM,OAAO,GAAG;GAC1B;EACD,CACD;CACH;CACA,MAAM,QAAsB,CAC3B;EAAE,QAAQ,SAAS;EAAK,MAAM,CAAC;EAAG,OAAO,QAAQ,MAAM;CAAE,CAC1D;CACA,OAAO,IAAI,QACV,IAAI,OAAO,QAAQ,SAAS;EAC3B,MAAM,QAAQ,KAAK,IAAI;EACvB,IAAI,UAAU,KAAA,GACb,OAAO,OAAO,KAAK;EAKpB,IAAI,QAAQ,IAAI,QAAQ,MAAM,KAAK,CAAC,IAAI,SAAS;GAChD,KAAK,KAAK,IAAI;EACf,CAAC;EACD,MAAM,QACL,MAAM,KAAK,WAAW,IACnB,CAAC,IACD,CAAC,QAAQ,MAAM,QAAQ,MAAM,MAAM,KAAK,CAAC;EAC7C,IAAI,MAAM,SAAS,KAAK,KAAK,SAAS,IAAA,SAAoB,eACzD,MAAMA,SACL,YACA,iBACA,UACA,KAAK,SAAS,GAAA,SACE,aACjB;EAED,OAAO,OAAO,KAAK,CAAC,OAAO,IAAI,CAAU;CAC1C,CAAC,CACF;AACD;;;;;AAMA,MAAa,YACZ,SACA,UAEA,UAAUC,iBACP,QAAQ,SACR,OAAO,QAAQ,UAAU,KAAK;;;;;;;AAQlC,MAAa,aAAa,QAAgC;CACzD,MAAM,WAAW,WAAW;EAC3B,SAAS,aAAa,qBAAK,IAAI,IAAI,CAAC;EACpC,SAAS;EACT,KAAK;EACL,OAAO;CACR,CAAC;CACD,OAAO;EACN,QAAQ,OAAO,UAAUA,cAAqB;EAC9C;EACA,OAAO,IAAI,OACV,UACA,IACC,OAAO,WACP,QACA,OAAO,SAAS,UACf,OAAO,cAAc,KAAA,IAAY,IAAI,EACxC;EACA,MAAM,SAAS;CAChB;AACD;;;;;;AAOA,MAAa,UACZ,SACA,MACA,QAAgBA,mBACO;CACvB,MAAM,QAAQ,KAAK,MAAM,KAAK,QAAQ,CAAC;CACvC,IAAI,MAAM,SAAS,OAClB;CAED,MAAM,QAAQ,IAAI,OACjB,OACA;EAAE,QAAQ,QAAQ;EAAM,MAAM,CAAC;CAAE,IAGhC,SAAS,SAAS;EAClB,IAAI,YAAY,KAAA,GACf;EAED,MAAM,QAAQ,QAAQ,IAAI,QAAQ,OAAO,OAAO,IAAI;EACpD,IAAI,OAAO,OAAO,KAAK,GACtB,OAAO;GACN,QAAQ,QAAQ,SAAS,MAAM;GAC/B,MAAM,CAAC,GAAG,QAAQ,MAAM,IAAI;EAC7B;EAED,MAAM,MAAM,QAAQ,OAAO;EAC3B,MAAM,YAAY,QAAQ,OAAO;EACjC,OAAO,QAAQ,KAAA,KAAa,cAAc,KAAA,KAAa,QAAQ,KAAK,IAAI,IACrE;GAAE,QAAQ,QAAQ,SAAS;GAAa,MAAM,CAAC,GAAG,QAAQ,MAAM,GAAG;EAAE,IACrE,KAAA;CACJ,CACD;CACA,OAAO,UAAU,KAAA,IACd,KAAA,IACA,QAAQ,MAAM,QAAQ,MAAM,MAAM,KAAK;AAC3C"}
@@ -1,5 +1,5 @@
1
1
  import { Allow, Deny, NotApplicable } from "../Decision.js";
2
- import { lookup } from "./catalog.js";
2
+ import { fieldsOf, lookup } from "./catalog.js";
3
3
  import { at, pathOf } from "./place.js";
4
4
  import { kindOf } from "./resource.js";
5
5
  import { evaluate } from "../Condition.js";
@@ -149,7 +149,7 @@ const permittedFields = (policy, request) => {
149
149
  const descriptor = kind === void 0 ? void 0 : descriptorOf(policy.domain, kind);
150
150
  if (descriptor === void 0) return Effect.succeed([]);
151
151
  const rules = policy.pipe(nodeOf, rulesOf);
152
- const listed = Arr.filter(descriptor.catalog.fields, (field) => field.listed && Arr.every(field.path, (segment) => typeof segment === "string"));
152
+ const listed = Arr.filter(fieldsOf(descriptor.catalog, policy.domain.limits.fieldPathParts), (field) => field.listed && Arr.every(field.path, (segment) => typeof segment === "string"));
153
153
  return Effect.map(Effect.filter(listed, (field) => Effect.map(decideAny(policy, {
154
154
  ...request,
155
155
  field: field.text
@@ -1 +1 @@
1
- {"version":3,"file":"decide.js","names":["RuleData.matchesTarget","Condition.evaluate","Place.pathOf","Decision.Allow","Decision.Deny","Place.at","at","Decision.NotApplicable","Catalog.lookup","ResourceData.kindOf","DomainData.descriptorOf","RuleData.fitsOf","RuleData.matchesInside","PolicyNodes.rulesOf"],"sources":["../../src/internal/decide.ts"],"sourcesContent":["/**\n * The decision walk of a policy: `decide`, `explain` and `permittedFields`.\n * The types are in `Policy.ts`.\n */\n// A policy keeps the error type and the service type of its predicates in the\n// public types. The code below works on the nodes with no types, where they\n// are `unknown`, so the language service check for `unknown` in an Effect is\n// off here.\n// @effect-diagnostics anyUnknownInErrorContext:off\nimport * as Arr from \"effect/Array\"\nimport * as Chunk from \"effect/Chunk\"\nimport * as Effect from \"effect/Effect\"\nimport * as Option from \"effect/Option\"\nimport * as Ref from \"effect/Ref\"\nimport * as Condition from \"../Condition.ts\"\nimport * as Decision from \"../Decision.ts\"\nimport type * as Domain from \"../Domain.ts\"\nimport type * as Policy from \"../Policy.ts\"\nimport type * as Resource from \"../Resource.ts\"\nimport * as Catalog from \"./catalog.ts\"\nimport * as DomainData from \"./domain.ts\"\nimport * as Place from \"./place.ts\"\nimport * as PolicyNodes from \"./policy-nodes.ts\"\nimport { nodeOf } from \"./policy.ts\"\nimport * as ResourceData from \"./resource.ts\"\nimport * as RuleData from \"./rule.ts\"\n\ntype Any = Policy.Any\ntype AnyRequest = Policy.AnyRequest\ntype AnyRule = Policy.AnyRule\ntype Explanation = Policy.Explanation\ntype Step = Policy.Step\n\ninterface DecideContext {\n\treadonly action: string\n\treadonly kind: string\n\treadonly field: string | undefined\n\treadonly input: Condition.Input\n\t/** The resource of the kind, for the fields that a rule may grant. */\n\treadonly descriptor: Resource.Descriptor\n\t/** The test of `RuleData.fitsOf` for the catalog, for this decision. */\n\treadonly fits: (pattern: string) => boolean\n\t/** The most parts of a field path in the domain. */\n\treadonly parts: number\n\t/** The rules that were tested, for {@link explain}. */\n\treadonly trace: Ref.Ref<Chunk.Chunk<Step>> | undefined\n}\n\n/**\n * The place of a node or a rule in a tree: the index of each child, then\n * the index of the rule. It shares the path of its parent, and a decision\n * makes the full path only for a step of `explain`: see `Place`.\n */\ntype At = Place.Place | undefined\n\nconst ruleApplies = (\n\tvalue: RuleData.Rule,\n\tcontext: DecideContext,\n\tat: Place.Place,\n): Effect.Effect<boolean, unknown, unknown> => {\n\tif (!RuleData.matchesTarget(value, context)) {\n\t\treturn Effect.succeed(false)\n\t}\n\tconst test =\n\t\tvalue.when === undefined\n\t\t\t? Effect.succeed(true)\n\t\t\t: // A rule of a node keeps no type of what it reads. The decision gives\n\t\t\t\t// it the value and the principal of the request.\n\t\t\t\tCondition.evaluate(value.when as Condition.Any, context.input)\n\tconst trace = context.trace\n\treturn trace === undefined\n\t\t? test\n\t\t: Effect.tap(test, (applies) =>\n\t\t\t\t// A chunk adds a step with no copy of the steps before.\n\t\t\t\tRef.update(\n\t\t\t\t\ttrace,\n\t\t\t\t\tChunk.append({\n\t\t\t\t\t\tpath: Place.pathOf(at) as ReadonlyArray<number>,\n\t\t\t\t\t\trule: typed(value),\n\t\t\t\t\t\tapplies,\n\t\t\t\t\t}),\n\t\t\t\t),\n\t\t\t)\n}\n\n/** A rule of a node, with the type of a rule of no domain. */\nconst typed = (value: RuleData.Rule): AnyRule => value as never\n\nconst fromRule = (value: RuleData.Rule): Decision.Decision =>\n\tvalue._tag === \"Allow\"\n\t\t? Decision.Allow({ rule: typed(value) })\n\t\t: value.reason === undefined\n\t\t\t? Decision.Deny({ rule: typed(value) })\n\t\t\t: Decision.Deny({ rule: typed(value), reason: value.reason })\n\n/** Gives the first rule that applies, in order. */\nconst firstApplying = (\n\tcandidates: ReadonlyArray<readonly [RuleData.Rule, number]>,\n\tcontext: DecideContext,\n\tat: At,\n): Effect.Effect<Option.Option<RuleData.Rule>, unknown, unknown> =>\n\tEffect.map(\n\t\tEffect.findFirst(candidates, ([value, index]) =>\n\t\t\truleApplies(value, context, Place.at(at, index)),\n\t\t),\n\t\tOption.map(([value]) => value),\n\t)\n\n/** Gives the first result that is found, and tests `next` only when none is. */\nconst orElse = <A, E, R>(\n\tfirst: Effect.Effect<Option.Option<A>, E, R>,\n\tnext: () => Effect.Effect<Option.Option<A>, E, R>,\n): Effect.Effect<Option.Option<A>, E, R> =>\n\tEffect.filterOrElse(first, Option.isSome, next)\n\nconst decideRules = (\n\tnode: PolicyNodes.Rules,\n\tcontext: DecideContext,\n\tat: At,\n): Effect.Effect<Decision.Decision, unknown, unknown> => {\n\tconst indexed = Arr.map(node.rules, (value, index) => [value, index] as const)\n\tconst denies = Arr.filter(indexed, ([value]) => value._tag === \"Deny\")\n\tconst allows = Arr.filter(indexed, ([value]) => value._tag === \"Allow\")\n\tconst first = (candidates: ReadonlyArray<readonly [RuleData.Rule, number]>) =>\n\t\tfirstApplying(candidates, context, at)\n\tconst found = (() => {\n\t\tswitch (node.algorithm) {\n\t\t\tcase \"denyOverrides\":\n\t\t\t\treturn orElse(first(denies), () => first(allows))\n\t\t\tcase \"permitOverrides\":\n\t\t\t\treturn orElse(first(allows), () => first(denies))\n\t\t\tcase \"firstApplicable\":\n\t\t\t\treturn first(indexed)\n\t\t\tcase \"lastApplicable\":\n\t\t\t\treturn first(Arr.reverse(indexed))\n\t\t}\n\t})()\n\treturn Effect.map(\n\t\tfound,\n\t\tOption.match({ onNone: () => Decision.NotApplicable, onSome: fromRule }),\n\t)\n}\n\n/** The next step of a walk over children: stop with a decision, or go on. */\ntype WalkStep<S> =\n\t| { readonly _tag: \"Stop\"; readonly decision: Decision.Decision }\n\t| { readonly _tag: \"Next\"; readonly state: S }\n\n/**\n * Decides the children in order. After each child, `step` stops with a\n * decision, or gives the state for the next child. After the last child,\n * `end` gives the decision. A child after a stop is not decided, so its\n * predicates do not run.\n */\nconst walkChildren = <S>(\n\tchildren: ReadonlyArray<PolicyNodes.Node>,\n\tcontext: DecideContext,\n\tat: At,\n\tstart: S,\n\tstep: (state: S, decision: Decision.Decision) => WalkStep<S>,\n\tend: (state: S) => Decision.Decision,\n): Effect.Effect<Decision.Decision, unknown, unknown> => {\n\tconst go = (\n\t\tindex: number,\n\t\tstate: S,\n\t): Effect.Effect<Decision.Decision, unknown, unknown> =>\n\t\tindex === children.length\n\t\t\t? Effect.succeed(end(state))\n\t\t\t: Effect.flatMap(\n\t\t\t\t\t// Each call for a child is a step of the Effect, so a deep tree\n\t\t\t\t\t// does not fill the stack.\n\t\t\t\t\tEffect.suspend(() =>\n\t\t\t\t\t\tdecideNode(children[index]!, context, Place.at(at, index)),\n\t\t\t\t\t),\n\t\t\t\t\t(decision) => {\n\t\t\t\t\t\tconst next = step(state, decision)\n\t\t\t\t\t\treturn next._tag === \"Stop\"\n\t\t\t\t\t\t\t? Effect.succeed(next.decision)\n\t\t\t\t\t\t\t: go(index + 1, next.state)\n\t\t\t\t\t},\n\t\t\t\t)\n\treturn go(0, start)\n}\n\nconst stop = (decision: Decision.Decision): WalkStep<never> => ({\n\t_tag: \"Stop\",\n\tdecision,\n})\n\nconst next = <S>(state: S): WalkStep<S> => ({ _tag: \"Next\", state })\n\n/**\n * Decides a node. A shared child is decided again for each place, so its\n * predicates run each time, as the policy says. The limits of the nodes keep\n * this work small.\n */\nexport const decideNode = (\n\tnode: PolicyNodes.Node,\n\tcontext: DecideContext,\n\tat: At,\n): Effect.Effect<Decision.Decision, unknown, unknown> => {\n\tswitch (node._tag) {\n\t\tcase \"Rules\":\n\t\t\treturn decideRules(node, context, at)\n\t\tcase \"All\":\n\t\t\t// One deny is enough to deny. Every child must allow.\n\t\t\treturn walkChildren(\n\t\t\t\tnode.policies,\n\t\t\t\tcontext,\n\t\t\t\tat,\n\t\t\t\t{\n\t\t\t\t\tfirstAllow: Option.none<Decision.Decision>(),\n\t\t\t\t\tallAllow: node.policies.length > 0,\n\t\t\t\t},\n\t\t\t\t(state, decision) =>\n\t\t\t\t\tdecision._tag === \"Deny\"\n\t\t\t\t\t\t? stop(decision)\n\t\t\t\t\t\t: next({\n\t\t\t\t\t\t\t\tfirstAllow: Option.orElse(state.firstAllow, () =>\n\t\t\t\t\t\t\t\t\tdecision._tag === \"Allow\"\n\t\t\t\t\t\t\t\t\t\t? Option.some(decision)\n\t\t\t\t\t\t\t\t\t\t: Option.none(),\n\t\t\t\t\t\t\t\t),\n\t\t\t\t\t\t\t\tallAllow: state.allAllow && decision._tag === \"Allow\",\n\t\t\t\t\t\t\t}),\n\t\t\t\t(state) =>\n\t\t\t\t\tstate.allAllow\n\t\t\t\t\t\t? Option.getOrElse(state.firstAllow, () => Decision.NotApplicable)\n\t\t\t\t\t\t: Decision.NotApplicable,\n\t\t\t)\n\t\tcase \"Any\":\n\t\t\t// One allow is enough. Without one, the first deny denies.\n\t\t\treturn walkChildren(\n\t\t\t\tnode.policies,\n\t\t\t\tcontext,\n\t\t\t\tat,\n\t\t\t\tOption.none<Decision.Decision>(),\n\t\t\t\t(firstDeny, decision) =>\n\t\t\t\t\tdecision._tag === \"Allow\"\n\t\t\t\t\t\t? stop(decision)\n\t\t\t\t\t\t: next(\n\t\t\t\t\t\t\t\tOption.orElse(firstDeny, () =>\n\t\t\t\t\t\t\t\t\tdecision._tag === \"Deny\"\n\t\t\t\t\t\t\t\t\t\t? Option.some(decision)\n\t\t\t\t\t\t\t\t\t\t: Option.none(),\n\t\t\t\t\t\t\t\t),\n\t\t\t\t\t\t\t),\n\t\t\t\t(firstDeny) =>\n\t\t\t\t\tOption.getOrElse(firstDeny, () => Decision.NotApplicable),\n\t\t\t)\n\t\tcase \"FirstOf\":\n\t\t\treturn walkChildren(\n\t\t\t\tnode.policies,\n\t\t\t\tcontext,\n\t\t\t\tat,\n\t\t\t\tundefined,\n\t\t\t\t(_, decision) =>\n\t\t\t\t\tdecision._tag === \"NotApplicable\" ? next(undefined) : stop(decision),\n\t\t\t\t() => Decision.NotApplicable,\n\t\t\t)\n\t\tcase \"Ceiling\":\n\t\t\t// A deny of the cap wins. Else the policy decides.\n\t\t\treturn Effect.filterOrElse(\n\t\t\t\tEffect.suspend(() => decideNode(node.cap, context, Place.at(at, 0))),\n\t\t\t\t(capped) => capped._tag === \"Deny\",\n\t\t\t\t() => decideNode(node.policy, context, Place.at(at, 1)),\n\t\t\t)\n\t}\n}\n\n/**\n * Tests if a request fits its kind: the action is an action of the kind, and\n * the field, if there is one, is a field of the kind.\n */\nexport const fitsKind = (\n\tdescriptor: Resource.Descriptor,\n\trequest: { readonly action: string; readonly field?: string | undefined },\n\tparts: number,\n): boolean =>\n\tdescriptor.actions.includes(request.action) &&\n\t(request.field === undefined ||\n\t\t(Catalog.lookup(descriptor.catalog, request.field, parts)?.listed ?? false))\n\nconst makeContext = (\n\tdomain: Domain.Any,\n\trequest: AnyRequest,\n\ttrace: Ref.Ref<Chunk.Chunk<Step>> | undefined,\n): DecideContext | undefined => {\n\tconst kind = ResourceData.kindOf(request.resource)\n\tconst descriptor =\n\t\tkind === undefined ? undefined : DomainData.descriptorOf(domain, kind)\n\tif (\n\t\tkind === undefined ||\n\t\tdescriptor === undefined ||\n\t\t!fitsKind(descriptor, request, domain.limits.fieldPathParts)\n\t) {\n\t\treturn undefined\n\t}\n\treturn {\n\t\taction: request.action,\n\t\tkind,\n\t\tfield: request.field,\n\t\tinput: {\n\t\t\tvalue: request.resource,\n\t\t\tprincipal: request.principal,\n\t\t\tpath: descriptor.path,\n\t\t},\n\t\tdescriptor,\n\t\tfits: RuleData.fitsOf(descriptor.catalog, domain.limits.fieldPathParts),\n\t\tparts: domain.limits.fieldPathParts,\n\t\ttrace,\n\t}\n}\n\nexport const decideAny = (\n\tpolicy: Any,\n\trequest: AnyRequest,\n): Effect.Effect<Decision.Decision, unknown, unknown> =>\n\t// The context reads the resource value, so it is made for each run.\n\tEffect.suspend(() => {\n\t\tconst context = makeContext(policy.domain, request, undefined)\n\t\treturn context === undefined\n\t\t\t? Effect.succeed(Decision.NotApplicable)\n\t\t\t: decideNode(nodeOf(policy), context, undefined)\n\t})\n\n/** Gives the decision of a policy with no predicates, at once. */\nexport const decideSync = (\n\tpolicy: Any,\n\trequest: AnyRequest,\n): Decision.Decision =>\n\tEffect.runSync(\n\t\t// The public type of `decideSync` takes only policies with no\n\t\t// predicates, so this Effect has no error and needs no services.\n\t\t// @effect-diagnostics-next-line unsafeEffectTypeAssertion:off\n\t\tdecideAny(policy, request) as Effect.Effect<Decision.Decision>,\n\t)\n\n/** Gives the decision of a policy and the rules that were tested to make it. */\nexport const explain = (\n\tpolicy: Any,\n\trequest: AnyRequest,\n): Effect.Effect<Explanation, unknown, unknown> =>\n\t// Each run of the Effect needs its own trace, so the trace is made inside\n\t// the Effect.\n\tEffect.gen(function* () {\n\t\tconst trace = yield* Ref.make(Chunk.empty<Step>())\n\t\tconst context = makeContext(policy.domain, request, trace)\n\t\tif (context === undefined) {\n\t\t\treturn { decision: Decision.NotApplicable, steps: [] }\n\t\t}\n\t\tconst decision = yield* decideNode(nodeOf(policy), context, undefined)\n\t\treturn { decision, steps: Chunk.toReadonlyArray(yield* Ref.get(trace)) }\n\t})\n\n/**\n * Tests if a deny rule of the request names a part inside `field`.\n */\nconst deniesInside = (\n\trules: ReadonlyArray<RuleData.Rule>,\n\ttarget: { readonly action: string; readonly kind: string },\n\tfield: string,\n): boolean =>\n\tArr.some(\n\t\trules,\n\t\t(value) =>\n\t\t\tvalue._tag === \"Deny\" &&\n\t\t\tvalue.fields !== undefined &&\n\t\t\tvalue.fields.some((pattern) => RuleData.matchesInside(pattern, field)) &&\n\t\t\t(value.actions.includes(\"*\") || value.actions.includes(target.action)) &&\n\t\t\t(value.resources.includes(\"*\") || value.resources.includes(target.kind)),\n\t)\n\n/** Gives the fields of a resource value that the policy lets a request use. */\nexport const permittedFields = (\n\tpolicy: Any,\n\trequest: Omit<AnyRequest, \"field\">,\n): Effect.Effect<ReadonlyArray<string>, unknown, unknown> => {\n\tconst kind = ResourceData.kindOf(request.resource)\n\tconst descriptor: Resource.Descriptor | undefined =\n\t\tkind === undefined\n\t\t\t? undefined\n\t\t\t: DomainData.descriptorOf(policy.domain, kind)\n\tif (descriptor === undefined) {\n\t\treturn Effect.succeed([])\n\t}\n\t// The rules of the tree, in one flat list for all the fields.\n\tconst rules = policy.pipe(nodeOf, PolicyNodes.rulesOf)\n\t// The fields with names only: a list can not name every key of a record.\n\tconst listed = Arr.filter(\n\t\tdescriptor.catalog.fields,\n\t\t(field) =>\n\t\t\tfield.listed &&\n\t\t\tArr.every(field.path, (segment) => typeof segment === \"string\"),\n\t)\n\treturn Effect.map(\n\t\tEffect.filter(listed, (field) =>\n\t\t\tEffect.map(\n\t\t\t\tdecideAny(policy, { ...request, field: field.text }),\n\t\t\t\t(result) =>\n\t\t\t\t\tresult._tag === \"Allow\" &&\n\t\t\t\t\t!(\n\t\t\t\t\t\tfield.open &&\n\t\t\t\t\t\tdeniesInside(\n\t\t\t\t\t\t\trules,\n\t\t\t\t\t\t\t{ ...request, kind: descriptor.kind },\n\t\t\t\t\t\t\tfield.text,\n\t\t\t\t\t\t)\n\t\t\t\t\t),\n\t\t\t),\n\t\t),\n\t\tArr.map((field) => field.text),\n\t)\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;AAuDA,MAAM,eACL,OACA,SACA,OAC8C;CAC9C,IAAI,CAACA,cAAuB,OAAO,OAAO,GACzC,OAAO,OAAO,QAAQ,KAAK;CAE5B,MAAM,OACL,MAAM,SAAS,KAAA,IACZ,OAAO,QAAQ,IAAI,IAGpBC,SAAmB,MAAM,MAAuB,QAAQ,KAAK;CAChE,MAAM,QAAQ,QAAQ;CACtB,OAAO,UAAU,KAAA,IACd,OACA,OAAO,IAAI,OAAO,YAElB,IAAI,OACH,OACA,MAAM,OAAO;EACZ,MAAMC,OAAa,EAAE;EACrB,MAAM,MAAM,KAAK;EACjB;CACD,CAAC,CACF,CACD;AACH;;AAGA,MAAM,SAAS,UAAkC;AAEjD,MAAM,YAAY,UACjB,MAAM,SAAS,UACZC,MAAe,EAAE,MAAM,MAAM,KAAK,EAAE,CAAC,IACrC,MAAM,WAAW,KAAA,IAChBC,KAAc,EAAE,MAAM,MAAM,KAAK,EAAE,CAAC,IACpCA,KAAc;CAAE,MAAM,MAAM,KAAK;CAAG,QAAQ,MAAM;AAAO,CAAC;;AAG/D,MAAM,iBACL,YACA,SACA,SAEA,OAAO,IACN,OAAO,UAAU,aAAa,CAAC,OAAO,WACrC,YAAY,OAAO,SAASC,GAASC,MAAI,KAAK,CAAC,CAChD,GACA,OAAO,KAAK,CAAC,WAAW,KAAK,CAC9B;;AAGD,MAAM,UACL,OACA,SAEA,OAAO,aAAa,OAAO,OAAO,QAAQ,IAAI;AAE/C,MAAM,eACL,MACA,SACA,OACwD;CACxD,MAAM,UAAU,IAAI,IAAI,KAAK,QAAQ,OAAO,UAAU,CAAC,OAAO,KAAK,CAAU;CAC7E,MAAM,SAAS,IAAI,OAAO,UAAU,CAAC,WAAW,MAAM,SAAS,MAAM;CACrE,MAAM,SAAS,IAAI,OAAO,UAAU,CAAC,WAAW,MAAM,SAAS,OAAO;CACtE,MAAM,SAAS,eACd,cAAc,YAAY,SAAS,EAAE;CACtC,MAAM,eAAe;EACpB,QAAQ,KAAK,WAAb;GACC,KAAK,iBACJ,OAAO,OAAO,MAAM,MAAM,SAAS,MAAM,MAAM,CAAC;GACjD,KAAK,mBACJ,OAAO,OAAO,MAAM,MAAM,SAAS,MAAM,MAAM,CAAC;GACjD,KAAK,mBACJ,OAAO,MAAM,OAAO;GACrB,KAAK,kBACJ,OAAO,MAAM,IAAI,QAAQ,OAAO,CAAC;EACnC;CACD,EAAA,CAAG;CACH,OAAO,OAAO,IACb,OACA,OAAO,MAAM;EAAE,cAAcC;EAAwB,QAAQ;CAAS,CAAC,CACxE;AACD;;;;;;;AAaA,MAAM,gBACL,UACA,SACA,MACA,OACA,MACA,QACwD;CACxD,MAAM,MACL,OACA,UAEA,UAAU,SAAS,SAChB,OAAO,QAAQ,IAAI,KAAK,CAAC,IACzB,OAAO,QAGP,OAAO,cACN,WAAW,SAAS,QAAS,SAASF,GAASC,MAAI,KAAK,CAAC,CAC1D,IACC,aAAa;EACb,MAAM,OAAO,KAAK,OAAO,QAAQ;EACjC,OAAO,KAAK,SAAS,SAClB,OAAO,QAAQ,KAAK,QAAQ,IAC5B,GAAG,QAAQ,GAAG,KAAK,KAAK;CAC5B,CACD;CACH,OAAO,GAAG,GAAG,KAAK;AACnB;AAEA,MAAM,QAAQ,cAAkD;CAC/D,MAAM;CACN;AACD;AAEA,MAAM,QAAW,WAA2B;CAAE,MAAM;CAAQ;AAAM;;;;;;AAOlE,MAAa,cACZ,MACA,SACA,SACwD;CACxD,QAAQ,KAAK,MAAb;EACC,KAAK,SACJ,OAAO,YAAY,MAAM,SAASA,IAAE;EACrC,KAAK,OAEJ,OAAO,aACN,KAAK,UACL,SACAA,MACA;GACC,YAAY,OAAO,KAAwB;GAC3C,UAAU,KAAK,SAAS,SAAS;EAClC,IACC,OAAO,aACP,SAAS,SAAS,SACf,KAAK,QAAQ,IACb,KAAK;GACL,YAAY,OAAO,OAAO,MAAM,kBAC/B,SAAS,SAAS,UACf,OAAO,KAAK,QAAQ,IACpB,OAAO,KAAK,CAChB;GACA,UAAU,MAAM,YAAY,SAAS,SAAS;EAC/C,CAAC,IACH,UACA,MAAM,WACH,OAAO,UAAU,MAAM,kBAAkBC,aAAsB,IAC/DA,aACL;EACD,KAAK,OAEJ,OAAO,aACN,KAAK,UACL,SACAD,MACA,OAAO,KAAwB,IAC9B,WAAW,aACX,SAAS,SAAS,UACf,KAAK,QAAQ,IACb,KACA,OAAO,OAAO,iBACb,SAAS,SAAS,SACf,OAAO,KAAK,QAAQ,IACpB,OAAO,KAAK,CAChB,CACD,IACF,cACA,OAAO,UAAU,iBAAiBC,aAAsB,CAC1D;EACD,KAAK,WACJ,OAAO,aACN,KAAK,UACL,SACAD,MACA,KAAA,IACC,GAAG,aACH,SAAS,SAAS,kBAAkB,KAAK,KAAA,CAAS,IAAI,KAAK,QAAQ,SAC9DC,aACP;EACD,KAAK,WAEJ,OAAO,OAAO,aACb,OAAO,cAAc,WAAW,KAAK,KAAK,SAASF,GAASC,MAAI,CAAC,CAAC,CAAC,IAClE,WAAW,OAAO,SAAS,cACtB,WAAW,KAAK,QAAQ,SAASD,GAASC,MAAI,CAAC,CAAC,CACvD;CACF;AACD;;;;;AAMA,MAAa,YACZ,YACA,SACA,UAEA,WAAW,QAAQ,SAAS,QAAQ,MAAM,MACzC,QAAQ,UAAU,KAAA,MACjBE,OAAe,WAAW,SAAS,QAAQ,OAAO,KAAK,CAAC,EAAE,UAAU;AAEvE,MAAM,eACL,QACA,SACA,UAC+B;CAC/B,MAAM,OAAOC,OAAoB,QAAQ,QAAQ;CACjD,MAAM,aACL,SAAS,KAAA,IAAY,KAAA,IAAYC,aAAwB,QAAQ,IAAI;CACtE,IACC,SAAS,KAAA,KACT,eAAe,KAAA,KACf,CAAC,SAAS,YAAY,SAAS,OAAO,OAAO,cAAc,GAE3D;CAED,OAAO;EACN,QAAQ,QAAQ;EAChB;EACA,OAAO,QAAQ;EACf,OAAO;GACN,OAAO,QAAQ;GACf,WAAW,QAAQ;GACnB,MAAM,WAAW;EAClB;EACA;EACA,MAAMC,OAAgB,WAAW,SAAS,OAAO,OAAO,cAAc;EACtE,OAAO,OAAO,OAAO;EACrB;CACD;AACD;AAEA,MAAa,aACZ,QACA,YAGA,OAAO,cAAc;CACpB,MAAM,UAAU,YAAY,OAAO,QAAQ,SAAS,KAAA,CAAS;CAC7D,OAAO,YAAY,KAAA,IAChB,OAAO,QAAQJ,aAAsB,IACrC,WAAW,OAAO,MAAM,GAAG,SAAS,KAAA,CAAS;AACjD,CAAC;;AAGF,MAAa,cACZ,QACA,YAEA,OAAO,QAIN,UAAU,QAAQ,OAAO,CAC1B;;AAGD,MAAa,WACZ,QACA,YAIA,OAAO,IAAI,aAAa;CACvB,MAAM,QAAQ,OAAO,IAAI,KAAK,MAAM,MAAY,CAAC;CACjD,MAAM,UAAU,YAAY,OAAO,QAAQ,SAAS,KAAK;CACzD,IAAI,YAAY,KAAA,GACf,OAAO;EAAE,UAAUA;EAAwB,OAAO,CAAC;CAAE;CAGtD,OAAO;EAAE,UAAA,OADe,WAAW,OAAO,MAAM,GAAG,SAAS,KAAA,CAAS;EAClD,OAAO,MAAM,gBAAgB,OAAO,IAAI,IAAI,KAAK,CAAC;CAAE;AACxE,CAAC;;;;AAKF,MAAM,gBACL,OACA,QACA,UAEA,IAAI,KACH,QACC,UACA,MAAM,SAAS,UACf,MAAM,WAAW,KAAA,KACjB,MAAM,OAAO,MAAM,YAAYK,cAAuB,SAAS,KAAK,CAAC,MACpE,MAAM,QAAQ,SAAS,GAAG,KAAK,MAAM,QAAQ,SAAS,OAAO,MAAM,OACnE,MAAM,UAAU,SAAS,GAAG,KAAK,MAAM,UAAU,SAAS,OAAO,IAAI,EACxE;;AAGD,MAAa,mBACZ,QACA,YAC4D;CAC5D,MAAM,OAAOH,OAAoB,QAAQ,QAAQ;CACjD,MAAM,aACL,SAAS,KAAA,IACN,KAAA,IACAC,aAAwB,OAAO,QAAQ,IAAI;CAC/C,IAAI,eAAe,KAAA,GAClB,OAAO,OAAO,QAAQ,CAAC,CAAC;CAGzB,MAAM,QAAQ,OAAO,KAAK,QAAQG,OAAmB;CAErD,MAAM,SAAS,IAAI,OAClB,WAAW,QAAQ,SAClB,UACA,MAAM,UACN,IAAI,MAAM,MAAM,OAAO,YAAY,OAAO,YAAY,QAAQ,CAChE;CACA,OAAO,OAAO,IACb,OAAO,OAAO,SAAS,UACtB,OAAO,IACN,UAAU,QAAQ;EAAE,GAAG;EAAS,OAAO,MAAM;CAAK,CAAC,IAClD,WACA,OAAO,SAAS,WAChB,EACC,MAAM,QACN,aACC,OACA;EAAE,GAAG;EAAS,MAAM,WAAW;CAAK,GACpC,MAAM,IACP,EAEH,CACD,GACA,IAAI,KAAK,UAAU,MAAM,IAAI,CAC9B;AACD"}
1
+ {"version":3,"file":"decide.js","names":["RuleData.matchesTarget","Condition.evaluate","Place.pathOf","Decision.Allow","Decision.Deny","Place.at","at","Decision.NotApplicable","Catalog.lookup","ResourceData.kindOf","DomainData.descriptorOf","RuleData.fitsOf","RuleData.matchesInside","PolicyNodes.rulesOf","Catalog.fieldsOf"],"sources":["../../src/internal/decide.ts"],"sourcesContent":["/**\n * The decision walk of a policy: `decide`, `explain` and `permittedFields`.\n * The types are in `Policy.ts`.\n */\n// A policy keeps the error type and the service type of its predicates in the\n// public types. The code below works on the nodes with no types, where they\n// are `unknown`, so the language service check for `unknown` in an Effect is\n// off here.\n// @effect-diagnostics anyUnknownInErrorContext:off\nimport * as Arr from \"effect/Array\"\nimport * as Chunk from \"effect/Chunk\"\nimport * as Effect from \"effect/Effect\"\nimport * as Option from \"effect/Option\"\nimport * as Ref from \"effect/Ref\"\nimport * as Condition from \"../Condition.ts\"\nimport * as Decision from \"../Decision.ts\"\nimport type * as Domain from \"../Domain.ts\"\nimport type * as Policy from \"../Policy.ts\"\nimport type * as Resource from \"../Resource.ts\"\nimport * as Catalog from \"./catalog.ts\"\nimport * as DomainData from \"./domain.ts\"\nimport * as Place from \"./place.ts\"\nimport * as PolicyNodes from \"./policy-nodes.ts\"\nimport { nodeOf } from \"./policy.ts\"\nimport * as ResourceData from \"./resource.ts\"\nimport * as RuleData from \"./rule.ts\"\n\ntype Any = Policy.Any\ntype AnyRequest = Policy.AnyRequest\ntype AnyRule = Policy.AnyRule\ntype Explanation = Policy.Explanation\ntype Step = Policy.Step\n\ninterface DecideContext {\n\treadonly action: string\n\treadonly kind: string\n\treadonly field: string | undefined\n\treadonly input: Condition.Input\n\t/** The resource of the kind, for the fields that a rule may grant. */\n\treadonly descriptor: Resource.Descriptor\n\t/** The test of `RuleData.fitsOf` for the catalog, for this decision. */\n\treadonly fits: (pattern: string) => boolean\n\t/** The most parts of a field path in the domain. */\n\treadonly parts: number\n\t/** The rules that were tested, for {@link explain}. */\n\treadonly trace: Ref.Ref<Chunk.Chunk<Step>> | undefined\n}\n\n/**\n * The place of a node or a rule in a tree: the index of each child, then\n * the index of the rule. It shares the path of its parent, and a decision\n * makes the full path only for a step of `explain`: see `Place`.\n */\ntype At = Place.Place | undefined\n\nconst ruleApplies = (\n\tvalue: RuleData.Rule,\n\tcontext: DecideContext,\n\tat: Place.Place,\n): Effect.Effect<boolean, unknown, unknown> => {\n\tif (!RuleData.matchesTarget(value, context)) {\n\t\treturn Effect.succeed(false)\n\t}\n\tconst test =\n\t\tvalue.when === undefined\n\t\t\t? Effect.succeed(true)\n\t\t\t: // A rule of a node keeps no type of what it reads. The decision gives\n\t\t\t\t// it the value and the principal of the request.\n\t\t\t\tCondition.evaluate(value.when as Condition.Any, context.input)\n\tconst trace = context.trace\n\treturn trace === undefined\n\t\t? test\n\t\t: Effect.tap(test, (applies) =>\n\t\t\t\t// A chunk adds a step with no copy of the steps before.\n\t\t\t\tRef.update(\n\t\t\t\t\ttrace,\n\t\t\t\t\tChunk.append({\n\t\t\t\t\t\tpath: Place.pathOf(at) as ReadonlyArray<number>,\n\t\t\t\t\t\trule: typed(value),\n\t\t\t\t\t\tapplies,\n\t\t\t\t\t}),\n\t\t\t\t),\n\t\t\t)\n}\n\n/** A rule of a node, with the type of a rule of no domain. */\nconst typed = (value: RuleData.Rule): AnyRule => value as never\n\nconst fromRule = (value: RuleData.Rule): Decision.Decision =>\n\tvalue._tag === \"Allow\"\n\t\t? Decision.Allow({ rule: typed(value) })\n\t\t: value.reason === undefined\n\t\t\t? Decision.Deny({ rule: typed(value) })\n\t\t\t: Decision.Deny({ rule: typed(value), reason: value.reason })\n\n/** Gives the first rule that applies, in order. */\nconst firstApplying = (\n\tcandidates: ReadonlyArray<readonly [RuleData.Rule, number]>,\n\tcontext: DecideContext,\n\tat: At,\n): Effect.Effect<Option.Option<RuleData.Rule>, unknown, unknown> =>\n\tEffect.map(\n\t\tEffect.findFirst(candidates, ([value, index]) =>\n\t\t\truleApplies(value, context, Place.at(at, index)),\n\t\t),\n\t\tOption.map(([value]) => value),\n\t)\n\n/** Gives the first result that is found, and tests `next` only when none is. */\nconst orElse = <A, E, R>(\n\tfirst: Effect.Effect<Option.Option<A>, E, R>,\n\tnext: () => Effect.Effect<Option.Option<A>, E, R>,\n): Effect.Effect<Option.Option<A>, E, R> =>\n\tEffect.filterOrElse(first, Option.isSome, next)\n\nconst decideRules = (\n\tnode: PolicyNodes.Rules,\n\tcontext: DecideContext,\n\tat: At,\n): Effect.Effect<Decision.Decision, unknown, unknown> => {\n\tconst indexed = Arr.map(node.rules, (value, index) => [value, index] as const)\n\tconst denies = Arr.filter(indexed, ([value]) => value._tag === \"Deny\")\n\tconst allows = Arr.filter(indexed, ([value]) => value._tag === \"Allow\")\n\tconst first = (candidates: ReadonlyArray<readonly [RuleData.Rule, number]>) =>\n\t\tfirstApplying(candidates, context, at)\n\tconst found = (() => {\n\t\tswitch (node.algorithm) {\n\t\t\tcase \"denyOverrides\":\n\t\t\t\treturn orElse(first(denies), () => first(allows))\n\t\t\tcase \"permitOverrides\":\n\t\t\t\treturn orElse(first(allows), () => first(denies))\n\t\t\tcase \"firstApplicable\":\n\t\t\t\treturn first(indexed)\n\t\t\tcase \"lastApplicable\":\n\t\t\t\treturn first(Arr.reverse(indexed))\n\t\t}\n\t})()\n\treturn Effect.map(\n\t\tfound,\n\t\tOption.match({ onNone: () => Decision.NotApplicable, onSome: fromRule }),\n\t)\n}\n\n/** The next step of a walk over children: stop with a decision, or go on. */\ntype WalkStep<S> =\n\t| { readonly _tag: \"Stop\"; readonly decision: Decision.Decision }\n\t| { readonly _tag: \"Next\"; readonly state: S }\n\n/**\n * Decides the children in order. After each child, `step` stops with a\n * decision, or gives the state for the next child. After the last child,\n * `end` gives the decision. A child after a stop is not decided, so its\n * predicates do not run.\n */\nconst walkChildren = <S>(\n\tchildren: ReadonlyArray<PolicyNodes.Node>,\n\tcontext: DecideContext,\n\tat: At,\n\tstart: S,\n\tstep: (state: S, decision: Decision.Decision) => WalkStep<S>,\n\tend: (state: S) => Decision.Decision,\n): Effect.Effect<Decision.Decision, unknown, unknown> => {\n\tconst go = (\n\t\tindex: number,\n\t\tstate: S,\n\t): Effect.Effect<Decision.Decision, unknown, unknown> =>\n\t\tindex === children.length\n\t\t\t? Effect.succeed(end(state))\n\t\t\t: Effect.flatMap(\n\t\t\t\t\t// Each call for a child is a step of the Effect, so a deep tree\n\t\t\t\t\t// does not fill the stack.\n\t\t\t\t\tEffect.suspend(() =>\n\t\t\t\t\t\tdecideNode(children[index]!, context, Place.at(at, index)),\n\t\t\t\t\t),\n\t\t\t\t\t(decision) => {\n\t\t\t\t\t\tconst next = step(state, decision)\n\t\t\t\t\t\treturn next._tag === \"Stop\"\n\t\t\t\t\t\t\t? Effect.succeed(next.decision)\n\t\t\t\t\t\t\t: go(index + 1, next.state)\n\t\t\t\t\t},\n\t\t\t\t)\n\treturn go(0, start)\n}\n\nconst stop = (decision: Decision.Decision): WalkStep<never> => ({\n\t_tag: \"Stop\",\n\tdecision,\n})\n\nconst next = <S>(state: S): WalkStep<S> => ({ _tag: \"Next\", state })\n\n/**\n * Decides a node. A shared child is decided again for each place, so its\n * predicates run each time, as the policy says. The limits of the nodes keep\n * this work small.\n */\nexport const decideNode = (\n\tnode: PolicyNodes.Node,\n\tcontext: DecideContext,\n\tat: At,\n): Effect.Effect<Decision.Decision, unknown, unknown> => {\n\tswitch (node._tag) {\n\t\tcase \"Rules\":\n\t\t\treturn decideRules(node, context, at)\n\t\tcase \"All\":\n\t\t\t// One deny is enough to deny. Every child must allow.\n\t\t\treturn walkChildren(\n\t\t\t\tnode.policies,\n\t\t\t\tcontext,\n\t\t\t\tat,\n\t\t\t\t{\n\t\t\t\t\tfirstAllow: Option.none<Decision.Decision>(),\n\t\t\t\t\tallAllow: node.policies.length > 0,\n\t\t\t\t},\n\t\t\t\t(state, decision) =>\n\t\t\t\t\tdecision._tag === \"Deny\"\n\t\t\t\t\t\t? stop(decision)\n\t\t\t\t\t\t: next({\n\t\t\t\t\t\t\t\tfirstAllow: Option.orElse(state.firstAllow, () =>\n\t\t\t\t\t\t\t\t\tdecision._tag === \"Allow\"\n\t\t\t\t\t\t\t\t\t\t? Option.some(decision)\n\t\t\t\t\t\t\t\t\t\t: Option.none(),\n\t\t\t\t\t\t\t\t),\n\t\t\t\t\t\t\t\tallAllow: state.allAllow && decision._tag === \"Allow\",\n\t\t\t\t\t\t\t}),\n\t\t\t\t(state) =>\n\t\t\t\t\tstate.allAllow\n\t\t\t\t\t\t? Option.getOrElse(state.firstAllow, () => Decision.NotApplicable)\n\t\t\t\t\t\t: Decision.NotApplicable,\n\t\t\t)\n\t\tcase \"Any\":\n\t\t\t// One allow is enough. Without one, the first deny denies.\n\t\t\treturn walkChildren(\n\t\t\t\tnode.policies,\n\t\t\t\tcontext,\n\t\t\t\tat,\n\t\t\t\tOption.none<Decision.Decision>(),\n\t\t\t\t(firstDeny, decision) =>\n\t\t\t\t\tdecision._tag === \"Allow\"\n\t\t\t\t\t\t? stop(decision)\n\t\t\t\t\t\t: next(\n\t\t\t\t\t\t\t\tOption.orElse(firstDeny, () =>\n\t\t\t\t\t\t\t\t\tdecision._tag === \"Deny\"\n\t\t\t\t\t\t\t\t\t\t? Option.some(decision)\n\t\t\t\t\t\t\t\t\t\t: Option.none(),\n\t\t\t\t\t\t\t\t),\n\t\t\t\t\t\t\t),\n\t\t\t\t(firstDeny) =>\n\t\t\t\t\tOption.getOrElse(firstDeny, () => Decision.NotApplicable),\n\t\t\t)\n\t\tcase \"FirstOf\":\n\t\t\treturn walkChildren(\n\t\t\t\tnode.policies,\n\t\t\t\tcontext,\n\t\t\t\tat,\n\t\t\t\tundefined,\n\t\t\t\t(_, decision) =>\n\t\t\t\t\tdecision._tag === \"NotApplicable\" ? next(undefined) : stop(decision),\n\t\t\t\t() => Decision.NotApplicable,\n\t\t\t)\n\t\tcase \"Ceiling\":\n\t\t\t// A deny of the cap wins. Else the policy decides.\n\t\t\treturn Effect.filterOrElse(\n\t\t\t\tEffect.suspend(() => decideNode(node.cap, context, Place.at(at, 0))),\n\t\t\t\t(capped) => capped._tag === \"Deny\",\n\t\t\t\t() => decideNode(node.policy, context, Place.at(at, 1)),\n\t\t\t)\n\t}\n}\n\n/**\n * Tests if a request fits its kind: the action is an action of the kind, and\n * the field, if there is one, is a field of the kind.\n */\nexport const fitsKind = (\n\tdescriptor: Resource.Descriptor,\n\trequest: { readonly action: string; readonly field?: string | undefined },\n\tparts: number,\n): boolean =>\n\tdescriptor.actions.includes(request.action) &&\n\t(request.field === undefined ||\n\t\t(Catalog.lookup(descriptor.catalog, request.field, parts)?.listed ?? false))\n\nconst makeContext = (\n\tdomain: Domain.Any,\n\trequest: AnyRequest,\n\ttrace: Ref.Ref<Chunk.Chunk<Step>> | undefined,\n): DecideContext | undefined => {\n\tconst kind = ResourceData.kindOf(request.resource)\n\tconst descriptor =\n\t\tkind === undefined ? undefined : DomainData.descriptorOf(domain, kind)\n\tif (\n\t\tkind === undefined ||\n\t\tdescriptor === undefined ||\n\t\t!fitsKind(descriptor, request, domain.limits.fieldPathParts)\n\t) {\n\t\treturn undefined\n\t}\n\treturn {\n\t\taction: request.action,\n\t\tkind,\n\t\tfield: request.field,\n\t\tinput: {\n\t\t\tvalue: request.resource,\n\t\t\tprincipal: request.principal,\n\t\t\tpath: descriptor.path,\n\t\t},\n\t\tdescriptor,\n\t\tfits: RuleData.fitsOf(descriptor.catalog, domain.limits.fieldPathParts),\n\t\tparts: domain.limits.fieldPathParts,\n\t\ttrace,\n\t}\n}\n\nexport const decideAny = (\n\tpolicy: Any,\n\trequest: AnyRequest,\n): Effect.Effect<Decision.Decision, unknown, unknown> =>\n\t// The context reads the resource value, so it is made for each run.\n\tEffect.suspend(() => {\n\t\tconst context = makeContext(policy.domain, request, undefined)\n\t\treturn context === undefined\n\t\t\t? Effect.succeed(Decision.NotApplicable)\n\t\t\t: decideNode(nodeOf(policy), context, undefined)\n\t})\n\n/** Gives the decision of a policy with no predicates, at once. */\nexport const decideSync = (\n\tpolicy: Any,\n\trequest: AnyRequest,\n): Decision.Decision =>\n\tEffect.runSync(\n\t\t// The public type of `decideSync` takes only policies with no\n\t\t// predicates, so this Effect has no error and needs no services.\n\t\t// @effect-diagnostics-next-line unsafeEffectTypeAssertion:off\n\t\tdecideAny(policy, request) as Effect.Effect<Decision.Decision>,\n\t)\n\n/** Gives the decision of a policy and the rules that were tested to make it. */\nexport const explain = (\n\tpolicy: Any,\n\trequest: AnyRequest,\n): Effect.Effect<Explanation, unknown, unknown> =>\n\t// Each run of the Effect needs its own trace, so the trace is made inside\n\t// the Effect.\n\tEffect.gen(function* () {\n\t\tconst trace = yield* Ref.make(Chunk.empty<Step>())\n\t\tconst context = makeContext(policy.domain, request, trace)\n\t\tif (context === undefined) {\n\t\t\treturn { decision: Decision.NotApplicable, steps: [] }\n\t\t}\n\t\tconst decision = yield* decideNode(nodeOf(policy), context, undefined)\n\t\treturn { decision, steps: Chunk.toReadonlyArray(yield* Ref.get(trace)) }\n\t})\n\n/**\n * Tests if a deny rule of the request names a part inside `field`.\n */\nconst deniesInside = (\n\trules: ReadonlyArray<RuleData.Rule>,\n\ttarget: { readonly action: string; readonly kind: string },\n\tfield: string,\n): boolean =>\n\tArr.some(\n\t\trules,\n\t\t(value) =>\n\t\t\tvalue._tag === \"Deny\" &&\n\t\t\tvalue.fields !== undefined &&\n\t\t\tvalue.fields.some((pattern) => RuleData.matchesInside(pattern, field)) &&\n\t\t\t(value.actions.includes(\"*\") || value.actions.includes(target.action)) &&\n\t\t\t(value.resources.includes(\"*\") || value.resources.includes(target.kind)),\n\t)\n\n/** Gives the fields of a resource value that the policy lets a request use. */\nexport const permittedFields = (\n\tpolicy: Any,\n\trequest: Omit<AnyRequest, \"field\">,\n): Effect.Effect<ReadonlyArray<string>, unknown, unknown> => {\n\tconst kind = ResourceData.kindOf(request.resource)\n\tconst descriptor: Resource.Descriptor | undefined =\n\t\tkind === undefined\n\t\t\t? undefined\n\t\t\t: DomainData.descriptorOf(policy.domain, kind)\n\tif (descriptor === undefined) {\n\t\treturn Effect.succeed([])\n\t}\n\t// The rules of the tree, in one flat list for all the fields.\n\tconst rules = policy.pipe(nodeOf, PolicyNodes.rulesOf)\n\t// The fields with names only: a list can not name every key of a record.\n\tconst listed = Arr.filter(\n\t\t// The list for the limit of the parts of a path of the domain, as the\n\t\t// decision of each field reads it.\n\t\tCatalog.fieldsOf(descriptor.catalog, policy.domain.limits.fieldPathParts),\n\t\t(field) =>\n\t\t\tfield.listed &&\n\t\t\tArr.every(field.path, (segment) => typeof segment === \"string\"),\n\t)\n\treturn Effect.map(\n\t\tEffect.filter(listed, (field) =>\n\t\t\tEffect.map(\n\t\t\t\tdecideAny(policy, { ...request, field: field.text }),\n\t\t\t\t(result) =>\n\t\t\t\t\tresult._tag === \"Allow\" &&\n\t\t\t\t\t!(\n\t\t\t\t\t\tfield.open &&\n\t\t\t\t\t\tdeniesInside(\n\t\t\t\t\t\t\trules,\n\t\t\t\t\t\t\t{ ...request, kind: descriptor.kind },\n\t\t\t\t\t\t\tfield.text,\n\t\t\t\t\t\t)\n\t\t\t\t\t),\n\t\t\t),\n\t\t),\n\t\tArr.map((field) => field.text),\n\t)\n}\n"],"mappings":"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"}
@@ -103,40 +103,40 @@ const matchesBelow = (pattern, names) => Arr.some(Arr.reduce(names, start(patter
103
103
  /**
104
104
  * Tests if a pattern matches some field of a catalog, with at most `parts`
105
105
  * parts. A recursive Schema has no end of fields, so the search goes over the
106
- * pairs of a place of the catalog and the places of the match: each pair
107
- * comes one time, a level after a level, so the search is short, and it
108
- * keeps a queue, so a deep Schema does not fill the stack. The set of the
109
- * pairs is native, with keys that are numbers, and the set and the queue
110
- * belong to this call.
106
+ * pairs of a place of the catalog and one place of the match. There are at
107
+ * most the places of the catalog times the parts of the pattern of them, so
108
+ * the work is the steps of the catalog times the parts of the pattern, as the
109
+ * binding counts it. Each pair comes one time, a level after a level, so the
110
+ * search finds the shortest field first, and it keeps a queue, so a deep
111
+ * Schema does not fill the stack. The set of the pairs is native, and the set
112
+ * and the queue belong to this call.
111
113
  */
112
114
  const fitsCatalog = (pattern, catalog, parts = fieldPathParts) => {
113
115
  const seen = /* @__PURE__ */ new Set();
116
+ const visits = (id, states, depth) => Arr.flatMap(states, (state) => {
117
+ const key = `${String(id)}|${String(state)}`;
118
+ if (seen.has(key)) return [];
119
+ seen.add(key);
120
+ return [{
121
+ branch: catalog.branches[id],
122
+ state,
123
+ depth
124
+ }];
125
+ });
114
126
  const next = (visit) => {
115
- if (visit.states.length === 0 || visit.depth === parts) return [];
116
- const steps = [...visit.branch.children, ...visit.branch.key === void 0 || visit.branch.keyBranch === void 0 ? [] : [[visit.branch.key, visit.branch.keyBranch]]];
117
- return Arr.flatMap(steps, ([segment, id]) => {
118
- const states = step(pattern, visit.states, segment);
119
- const key = `${String(id)}|${states.join(",")}`;
120
- if (states.length === 0 || seen.has(key)) return [];
121
- seen.add(key);
122
- return [{
123
- branch: catalog.branches[id],
124
- states,
125
- depth: visit.depth + 1
126
- }];
127
- });
127
+ if (visit.depth === parts) return [];
128
+ const branch = visit.branch;
129
+ const steps = branch.key === void 0 || branch.keyBranch === void 0 ? branch.children : [...branch.children, [branch.key, branch.keyBranch]];
130
+ return Arr.flatMap(steps, ([segment, id]) => visits(id, step(pattern, [visit.state], segment), visit.depth + 1));
128
131
  };
129
- const queue = [{
130
- branch: catalog.root,
131
- states: start(pattern),
132
- depth: 0
133
- }];
134
- const found = (visit) => visit.depth > 0 && isDone(pattern, visit.states);
132
+ const queue = [...visits(catalog.root.id, start(pattern), 0)];
135
133
  return Arr.some(Arr.unfold(0, (index) => {
136
134
  const visit = queue[index];
137
135
  if (visit === void 0) return Option.none();
138
- if (found(visit)) return Option.some([true, queue.length]);
139
- queue.push(...next(visit));
136
+ if (visit.depth > 0 && visit.state === pattern.length) return Option.some([true, queue.length]);
137
+ Arr.forEach(next(visit), (found) => {
138
+ queue.push(found);
139
+ });
140
140
  return Option.some([false, index + 1]);
141
141
  }), (match) => match);
142
142
  };