@nmnmcc/ability 2.1.3 → 2.1.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1 +1 @@
1
- {"version":3,"file":"json.js","names":["Nodes.toJson","Place.pathOf","Place.at","at","Nodes.isScalar","Nodes.literal","Nodes.principalRef","Trampoline.adaptive","Limits.LimitExceeded","Nodes.always","Nodes.never","Nodes.isCompareOperator","Nodes.Compare","Nodes.In","Nodes.Exists","Nodes.Under","Nodes.And","Nodes.Or","Nodes.Not","Limits.defaults"],"sources":["../../src/internal/json.ts"],"sourcesContent":["/**\n * The JSON form of a condition. The decoder is a walk of the trampoline, so a\n * deep input does not fill the stack. It reads at most `jsonNodes` objects\n * and array items, with a shared object counted for each place, so no input\n * can take a very long time. It makes\n * each node with its constructor, and it reads only the own data fields of\n * the input, as a condition reads a value.\n */\nimport * as Arr from \"effect/Array\"\nimport * as Data from \"effect/Data\"\nimport * as Result from \"effect/Result\"\nimport type * as SchemaIssue from \"effect/SchemaIssue\"\nimport * as Limits from \"./limits.ts\"\nimport * as Nodes from \"./nodes.ts\"\nimport * as Place from \"./place.ts\"\nimport { readPart } from \"./read.ts\"\nimport * as Trampoline from \"./trampoline.ts\"\n\ntype Node = Nodes.Node\n\n/** Gives the JSON form of a condition with no predicate. See `Nodes.toJson`. */\nexport const toJson = (node: Node): unknown => Nodes.toJson(node)\n\n/** A problem of an input, with the path of the problem. */\nexport class Problem extends Data.TaggedError(\"Problem\")<{\n\treadonly path: ReadonlyArray<PropertyKey>\n\treadonly message: string\n\t/** The issue of a Schema that the decode used, as for `meta`. */\n\treadonly issue?: SchemaIssue.Issue | undefined\n}> {}\n\n/**\n * The count of the objects and items that one decode has read, with the\n * limits of the decode. It belongs to one decode.\n */\nexport interface Budget {\n\tused: number\n\treadonly limits: Limits.Limits\n}\n\nexport const problem = (path: ReadonlyArray<PropertyKey>, message: string) =>\n\tnew Problem({ path, message })\n\n/**\n * The problem at a place of the input. A decoder keeps the place of each\n * part, which shares the path of its parent, and makes the full path only for\n * a problem: see `Place`.\n */\nexport const problemAt = (at: Place.Place | undefined, message: string) =>\n\tproblem(Place.pathOf(at), message)\n\n/** Counts `count` more objects or items, and stops past the limit. */\nexport const spend = (\n\tbudget: Budget,\n\tcount: number,\n\tat: Place.Place | undefined,\n): void => {\n\tbudget.used += count\n\tconst most = budget.limits.jsonNodes\n\tif (budget.used > most) {\n\t\tthrow problemAt(\n\t\t\tat,\n\t\t\t`The input has more than ${String(most)} objects and items. The most is ${String(most)}.`,\n\t\t)\n\t}\n}\n\n/**\n * Reads a field of the input at the place `at`. A field that is not plain\n * data is a problem.\n */\nexport const read = (\n\tinput: unknown,\n\tname: string,\n\tat: Place.Place | undefined,\n): unknown => {\n\ttry {\n\t\treturn readPart(input, name)\n\t} catch {\n\t\tthrow problemAt(Place.at(at, name), `The field \"${name}\" is not plain data`)\n\t}\n}\n\nexport const readString = (\n\tinput: unknown,\n\tname: string,\n\tat: Place.Place | undefined,\n): string => {\n\tconst value = read(input, name, at)\n\tif (typeof value !== \"string\") {\n\t\tthrow problemAt(Place.at(at, name), `The field \"${name}\" must be a string`)\n\t}\n\treturn value\n}\n\n/** Reads the items of an array. The place of an item is `at`, `name`, index. */\nexport const readList = (\n\tinput: unknown,\n\tname: string,\n\tat: Place.Place | undefined,\n\tbudget: Budget,\n): ReadonlyArray<unknown> => {\n\tconst value = read(input, name, at)\n\tconst place = Place.at(at, name)\n\tif (!Array.isArray(value)) {\n\t\tthrow problemAt(place, `The field \"${name}\" must be an array`)\n\t}\n\tconst list: ReadonlyArray<unknown> = value\n\tspend(budget, list.length, place)\n\t// `Array.makeBy` of Effect gives at least one item, so it can not make an\n\t// empty list. An array-like object with a length makes one with no read\n\t// of the items before the read rule.\n\treturn Array.from({ length: list.length }, (_, index) =>\n\t\tread(list, String(index), place),\n\t)\n}\n\n/**\n * Reads a field path, and stops one with more parts than the limit of the\n * decode. A node checks the ceiling only, since it does not know its domain.\n */\nconst readPath = (\n\tinput: unknown,\n\tname: string,\n\tat: Place.Place | undefined,\n\tbudget: Budget,\n): string => {\n\tconst path = readString(input, name, at)\n\tconst most = budget.limits.fieldPathParts\n\tconst parts = path.split(\".\", most + 1).length\n\tif (parts > most) {\n\t\tthrow problemAt(\n\t\t\tPlace.at(at, name),\n\t\t\t`A field path has more than ${String(most)} parts. The most is ${String(most)}.`,\n\t\t)\n\t}\n\treturn path\n}\n\nconst isJsonScalar = (value: unknown): value is Nodes.Scalar =>\n\tNodes.isScalar(value) && (typeof value !== \"number\" || Number.isFinite(value))\n\nconst decodeOperand = (\n\tinput: unknown,\n\tat: Place.Place | undefined,\n\tbudget: Budget,\n): Nodes.Operand => {\n\tconst tag = read(input, \"_tag\", at)\n\tif (tag === \"Literal\") {\n\t\t// A missing value is not `null`: a test with `null` has its own\n\t\t// meaning, and a lost value must not become one.\n\t\tconst own =\n\t\t\ttypeof input === \"object\" && input !== null\n\t\t\t\t? Object.getOwnPropertyDescriptor(input, \"value\")\n\t\t\t\t: undefined\n\t\tif (own === undefined || (\"value\" in own && own.value === undefined)) {\n\t\t\tthrow problemAt(Place.at(at, \"value\"), \"A fixed value is missing\")\n\t\t}\n\t\tconst value = read(input, \"value\", at)\n\t\tif (!isJsonScalar(value)) {\n\t\t\tthrow problemAt(\n\t\t\t\tPlace.at(at, \"value\"),\n\t\t\t\t\"A fixed value must be a string, a finite number, a boolean or null\",\n\t\t\t)\n\t\t}\n\t\treturn Nodes.literal(value)\n\t}\n\tif (tag === \"Principal\") {\n\t\treturn Nodes.principalRef(readPath(input, \"path\", at, budget))\n\t}\n\tthrow problemAt(\n\t\tPlace.at(at, \"_tag\"),\n\t\t'An operand must have the tag \"Literal\" or \"Principal\"',\n\t)\n}\n\nconst decodeOperands = (\n\tinput: unknown,\n\tname: string,\n\tat: Place.Place | undefined,\n\tbudget: Budget,\n): ReadonlyArray<Nodes.Operand> =>\n\tArr.map(readList(input, name, at, budget), (operand, index) =>\n\t\tdecodeOperand(operand, Place.at(at, name, index), budget),\n\t)\n\n/** A part of the input to decode, with its place. */\ninterface Part {\n\treadonly input: unknown\n\treadonly at: Place.Place | undefined\n}\n\n/**\n * The decoder of a condition, with the budget of one decode. It is a walk of\n * the trampoline for an input of unknown depth, so a deep input does not fill\n * the stack.\n */\nconst decoder = (budget: Budget): ((part: Part) => Node) => {\n\t// The nodes of this condition that the decoder has read. A condition with\n\t// more nodes than `conditionNodes` does not load, so the decoder stops at\n\t// the first node past it, and it does not read the rest of a very large\n\t// input. The count belongs to the decode of one condition.\n\tconst count = { nodes: 0 }\n\treturn Trampoline.adaptive<Part, Node>((R, child) => (part, depth) => {\n\t\tconst { input, at } = part\n\t\tif (typeof input !== \"object\" || input === null) {\n\t\t\tthrow problemAt(at, \"A condition node must be an object\")\n\t\t}\n\t\tspend(budget, 1, at)\n\t\tcount.nodes += 1\n\t\tif (count.nodes > budget.limits.conditionNodes) {\n\t\t\tthrow problemAt(\n\t\t\t\tat,\n\t\t\t\t`A condition has more than ${String(budget.limits.conditionNodes)} nodes and values. The most is ${String(budget.limits.conditionNodes)}.`,\n\t\t\t)\n\t\t}\n\t\t// A node past a limit, such as a field path with too many parts, is a\n\t\t// problem at its place. The constructor checks the ceilings, and the\n\t\t// decoder checks the limits of the decode.\n\t\tconst make = (build: () => Node): Node => {\n\t\t\tconst node = (() => {\n\t\t\t\ttry {\n\t\t\t\t\treturn build()\n\t\t\t\t} catch (error) {\n\t\t\t\t\tif (error instanceof Limits.LimitExceeded) {\n\t\t\t\t\t\tthrow problemAt(at, error.message)\n\t\t\t\t\t}\n\t\t\t\t\tthrow error\n\t\t\t\t}\n\t\t\t})()\n\t\t\tconst most = budget.limits.conditionNodes\n\t\t\tif (node.weight > most) {\n\t\t\t\tthrow problemAt(\n\t\t\t\t\tat,\n\t\t\t\t\t`A condition has ${String(node.weight)} nodes and values. The most is ${String(most)}.`,\n\t\t\t\t)\n\t\t\t}\n\t\t\treturn node\n\t\t}\n\t\tconst children = (name: string) =>\n\t\t\tR.forEach(readList(input, name, at, budget), (item, index) =>\n\t\t\t\tchild({ input: item, at: Place.at(at, name, index) }, depth + 1),\n\t\t\t)\n\t\tconst tag = read(input, \"_tag\", at)\n\t\tswitch (tag) {\n\t\t\tcase \"True\":\n\t\t\t\treturn R.succeed(Nodes.always)\n\t\t\tcase \"False\":\n\t\t\t\treturn R.succeed(Nodes.never)\n\t\t\tcase \"Compare\": {\n\t\t\t\tconst op = readString(input, \"op\", at)\n\t\t\t\tif (!Nodes.isCompareOperator(op)) {\n\t\t\t\t\tthrow problemAt(\n\t\t\t\t\t\tPlace.at(at, \"op\"),\n\t\t\t\t\t\t`\"${op}\" is not a compare operator`,\n\t\t\t\t\t)\n\t\t\t\t}\n\t\t\t\treturn R.succeed(\n\t\t\t\t\tmake(\n\t\t\t\t\t\t() =>\n\t\t\t\t\t\t\tnew Nodes.Compare({\n\t\t\t\t\t\t\t\top,\n\t\t\t\t\t\t\t\tfield: readPath(input, \"field\", at, budget),\n\t\t\t\t\t\t\t\tvalue: decodeOperand(\n\t\t\t\t\t\t\t\t\tread(input, \"value\", at),\n\t\t\t\t\t\t\t\t\tPlace.at(at, \"value\"),\n\t\t\t\t\t\t\t\t\tbudget,\n\t\t\t\t\t\t\t\t),\n\t\t\t\t\t\t\t}),\n\t\t\t\t\t),\n\t\t\t\t)\n\t\t\t}\n\t\t\tcase \"In\":\n\t\t\t\treturn R.succeed(\n\t\t\t\t\tmake(\n\t\t\t\t\t\t() =>\n\t\t\t\t\t\t\tnew Nodes.In({\n\t\t\t\t\t\t\t\tfield: readPath(input, \"field\", at, budget),\n\t\t\t\t\t\t\t\tvalues: decodeOperands(input, \"values\", at, budget),\n\t\t\t\t\t\t\t}),\n\t\t\t\t\t),\n\t\t\t\t)\n\t\t\tcase \"Exists\":\n\t\t\t\treturn R.succeed(\n\t\t\t\t\tmake(\n\t\t\t\t\t\t() =>\n\t\t\t\t\t\t\tnew Nodes.Exists({ field: readPath(input, \"field\", at, budget) }),\n\t\t\t\t\t),\n\t\t\t\t)\n\t\t\tcase \"Under\":\n\t\t\t\treturn R.succeed(\n\t\t\t\t\tmake(\n\t\t\t\t\t\t() =>\n\t\t\t\t\t\t\tnew Nodes.Under({\n\t\t\t\t\t\t\t\tprefix: decodeOperands(input, \"prefix\", at, budget),\n\t\t\t\t\t\t\t}),\n\t\t\t\t\t),\n\t\t\t\t)\n\t\t\tcase \"And\":\n\t\t\t\treturn R.map(children(\"conditions\"), (conditions) =>\n\t\t\t\t\tmake(() => new Nodes.And(conditions)),\n\t\t\t\t)\n\t\t\tcase \"Or\":\n\t\t\t\treturn R.map(children(\"conditions\"), (conditions) =>\n\t\t\t\t\tmake(() => new Nodes.Or(conditions)),\n\t\t\t\t)\n\t\t\tcase \"Not\":\n\t\t\t\treturn R.map(\n\t\t\t\t\tchild(\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tinput: read(input, \"condition\", at),\n\t\t\t\t\t\t\tat: Place.at(at, \"condition\"),\n\t\t\t\t\t\t},\n\t\t\t\t\t\tdepth + 1,\n\t\t\t\t\t),\n\t\t\t\t\t(condition) => make(() => new Nodes.Not(condition)),\n\t\t\t\t)\n\t\t\tdefault:\n\t\t\t\tthrow problemAt(Place.at(at, \"_tag\"), \"The node has no known tag\")\n\t\t}\n\t})\n}\n\n/**\n * Decodes the JSON form of a condition at a path, with the budget of a larger\n * decode. It throws a {@link Problem}.\n */\nexport const decodeAt = (\n\tinput: unknown,\n\tat: Place.Place | undefined,\n\tbudget: Budget,\n): Node => decoder(budget)({ input, at })\n\n/** Decodes the JSON form of a condition, with the default limits. */\nexport const decode = (input: unknown): Result.Result<Node, Problem> => {\n\ttry {\n\t\treturn Result.succeed(\n\t\t\tdecoder({ used: 0, limits: Limits.defaults })({ input, at: undefined }),\n\t\t)\n\t} catch (error) {\n\t\tif (error instanceof Problem) {\n\t\t\treturn Result.fail(error)\n\t\t}\n\t\tthrow error\n\t}\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"json.js","names":["Nodes.toJson","Place.pathOf","Place.at","at","Nodes.isScalar","Nodes.literal","Nodes.principalRef","Trampoline.adaptive","Limits.LimitExceeded","Nodes.always","Nodes.never","Nodes.isCompareOperator","Nodes.Compare","Nodes.In","Nodes.Exists","Nodes.Under","Nodes.And","Nodes.Or","Nodes.Not","Limits.defaults"],"sources":["../../src/internal/json.ts"],"sourcesContent":["/**\n * The JSON form of a condition. The decoder is a walk of the trampoline, so a\n * deep input does not fill the stack. It reads at most `jsonNodes` objects\n * and array items, with a shared object counted for each place, so no input\n * can take a very long time. It makes\n * each node with its constructor, and it reads only the own data fields of\n * the input, as a condition reads a value.\n */\nimport * as Arr from \"effect/Array\"\nimport * as Data from \"effect/Data\"\nimport * as Result from \"effect/Result\"\nimport type * as SchemaIssue from \"effect/SchemaIssue\"\nimport * as Limits from \"./limits.ts\"\nimport * as Nodes from \"./nodes.ts\"\nimport * as Place from \"./place.ts\"\nimport { readPart } from \"./read.ts\"\nimport * as Trampoline from \"./trampoline.ts\"\n\ntype Node = Nodes.Node\n\n/** Gives the JSON form of a condition with no predicate. See `Nodes.toJson`. */\nexport const toJson = (node: Node): unknown => Nodes.toJson(node)\n\n/** A problem of an input, with the path of the problem. */\nexport class Problem extends Data.TaggedError(\"Problem\")<{\n\treadonly path: ReadonlyArray<PropertyKey>\n\treadonly message: string\n\t/** The issue of a Schema that the decode used, as for `meta`. */\n\treadonly issue?: SchemaIssue.Issue | undefined\n}> {}\n\n/**\n * The count of the objects and items that one decode has read, with the\n * limits of the decode. It belongs to one decode.\n */\nexport interface Budget {\n\tused: number\n\treadonly limits: Limits.Limits\n}\n\nexport const problem = (path: ReadonlyArray<PropertyKey>, message: string) =>\n\tnew Problem({ path, message })\n\n/**\n * The problem at a place of the input. A decoder keeps the place of each\n * part, which shares the path of its parent, and makes the full path only for\n * a problem: see `Place`.\n */\nexport const problemAt = (at: Place.Place | undefined, message: string) =>\n\tproblem(Place.pathOf(at), message)\n\n/** Counts `count` more objects or items, and stops past the limit. */\nexport const spend = (\n\tbudget: Budget,\n\tcount: number,\n\tat: Place.Place | undefined,\n): void => {\n\tbudget.used += count\n\tconst most = budget.limits.jsonNodes\n\tif (budget.used > most) {\n\t\tthrow problemAt(\n\t\t\tat,\n\t\t\t`The input has more than ${String(most)} objects and items. The most is ${String(most)}.`,\n\t\t)\n\t}\n}\n\n/**\n * Reads a field of the input at the place `at`. A field that is not plain\n * data is a problem.\n */\nexport const read = (\n\tinput: unknown,\n\tname: string,\n\tat: Place.Place | undefined,\n): unknown => {\n\ttry {\n\t\treturn readPart(input, name)\n\t} catch {\n\t\tthrow problemAt(Place.at(at, name), `The field \"${name}\" is not plain data`)\n\t}\n}\n\nexport const readString = (\n\tinput: unknown,\n\tname: string,\n\tat: Place.Place | undefined,\n): string => {\n\tconst value = read(input, name, at)\n\tif (typeof value !== \"string\") {\n\t\tthrow problemAt(Place.at(at, name), `The field \"${name}\" must be a string`)\n\t}\n\treturn value\n}\n\n/** Reads the items of an array. The place of an item is `at`, `name`, index. */\nexport const readList = (\n\tinput: unknown,\n\tname: string,\n\tat: Place.Place | undefined,\n\tbudget: Budget,\n): ReadonlyArray<unknown> => {\n\tconst value = read(input, name, at)\n\tconst place = Place.at(at, name)\n\tif (!Array.isArray(value)) {\n\t\tthrow problemAt(place, `The field \"${name}\" must be an array`)\n\t}\n\tconst list: ReadonlyArray<unknown> = value\n\tspend(budget, list.length, place)\n\t// `Array.makeBy` of Effect gives at least one item, so it can not make an\n\t// empty list. An array-like object with a length makes one with no read\n\t// of the items before the read rule.\n\treturn Array.from({ length: list.length }, (_, index) =>\n\t\tread(list, String(index), place),\n\t)\n}\n\n/**\n * Reads a field path, and stops one with more parts than the limit of the\n * decode. A node checks the ceiling only, since it does not know its domain.\n */\nconst readPath = (\n\tinput: unknown,\n\tname: string,\n\tat: Place.Place | undefined,\n\tbudget: Budget,\n): string => {\n\tconst path = readString(input, name, at)\n\tconst most = budget.limits.fieldPathParts\n\tconst parts = path.split(\".\", most + 1).length\n\tif (parts > most) {\n\t\tthrow problemAt(\n\t\t\tPlace.at(at, name),\n\t\t\t`A field path has more than ${String(most)} parts. The most is ${String(most)}.`,\n\t\t)\n\t}\n\treturn path\n}\n\nconst isJsonScalar = (value: unknown): value is Nodes.Scalar =>\n\tNodes.isScalar(value) && (typeof value !== \"number\" || Number.isFinite(value))\n\nconst decodeOperand = (\n\tinput: unknown,\n\tat: Place.Place | undefined,\n\tbudget: Budget,\n): Nodes.Operand => {\n\tconst tag = read(input, \"_tag\", at)\n\tif (tag === \"Literal\") {\n\t\t// A missing value is not `null`: a test with `null` has its own\n\t\t// meaning, and a lost value must not become one.\n\t\tconst own =\n\t\t\ttypeof input === \"object\" && input !== null\n\t\t\t\t? Object.getOwnPropertyDescriptor(input, \"value\")\n\t\t\t\t: undefined\n\t\tif (own === undefined || (\"value\" in own && own.value === undefined)) {\n\t\t\tthrow problemAt(Place.at(at, \"value\"), \"A fixed value is missing\")\n\t\t}\n\t\tconst value = read(input, \"value\", at)\n\t\tif (!isJsonScalar(value)) {\n\t\t\tthrow problemAt(\n\t\t\t\tPlace.at(at, \"value\"),\n\t\t\t\t\"A fixed value must be a string, a finite number, a boolean or null\",\n\t\t\t)\n\t\t}\n\t\treturn Nodes.literal(value)\n\t}\n\tif (tag === \"Principal\") {\n\t\treturn Nodes.principalRef(readPath(input, \"path\", at, budget))\n\t}\n\tthrow problemAt(\n\t\tPlace.at(at, \"_tag\"),\n\t\t'An operand must have the tag \"Literal\" or \"Principal\"',\n\t)\n}\n\nconst decodeOperands = (\n\tinput: unknown,\n\tname: string,\n\tat: Place.Place | undefined,\n\tbudget: Budget,\n): ReadonlyArray<Nodes.Operand> =>\n\tArr.map(readList(input, name, at, budget), (operand, index) =>\n\t\tdecodeOperand(operand, Place.at(at, name, index), budget),\n\t)\n\n/** A part of the input to decode, with its place. */\ninterface Part {\n\treadonly input: unknown\n\treadonly at: Place.Place | undefined\n}\n\n/**\n * The decoder of a condition, with the budget of one decode. It is a walk of\n * the trampoline for an input of unknown depth, so a deep input does not fill\n * the stack.\n */\nconst decoder = (budget: Budget): ((part: Part) => Node) => {\n\t// The nodes of this condition that the decoder has read. A condition with\n\t// more nodes than `conditionNodes` does not load, so the decoder stops at\n\t// the first node past it, and it does not read the rest of a very large\n\t// input. The count belongs to the decode of one condition.\n\tconst count = { nodes: 0 }\n\treturn Trampoline.adaptive<Part, Node>((R, child) => (part, depth) => {\n\t\tconst { input, at } = part\n\t\tif (typeof input !== \"object\" || input === null) {\n\t\t\tthrow problemAt(at, \"A condition node must be an object\")\n\t\t}\n\t\tspend(budget, 1, at)\n\t\tconst counted = (more: number): void => {\n\t\t\tcount.nodes += more\n\t\t\tif (count.nodes > budget.limits.conditionNodes) {\n\t\t\t\tthrow problemAt(\n\t\t\t\t\tat,\n\t\t\t\t\t`A condition has more than ${String(budget.limits.conditionNodes)} nodes and values. The most is ${String(budget.limits.conditionNodes)}.`,\n\t\t\t\t)\n\t\t\t}\n\t\t}\n\t\tcounted(1)\n\t\t// The values of an `In` or an `Under` test count in the weight of the\n\t\t// condition. The length of the list counts before the decoder reads\n\t\t// any value, so a very long list stops before it takes memory.\n\t\tconst operands = (name: string): ReadonlyArray<Nodes.Operand> => {\n\t\t\tconst list = read(input, name, at)\n\t\t\tif (Array.isArray(list)) {\n\t\t\t\tcounted(list.length)\n\t\t\t}\n\t\t\treturn decodeOperands(input, name, at, budget)\n\t\t}\n\t\t// A node past a limit, such as a field path with too many parts, is a\n\t\t// problem at its place. The constructor checks the ceilings, and the\n\t\t// decoder checks the limits of the decode.\n\t\tconst make = (build: () => Node): Node => {\n\t\t\tconst node = (() => {\n\t\t\t\ttry {\n\t\t\t\t\treturn build()\n\t\t\t\t} catch (error) {\n\t\t\t\t\tif (error instanceof Limits.LimitExceeded) {\n\t\t\t\t\t\tthrow problemAt(at, error.message)\n\t\t\t\t\t}\n\t\t\t\t\tthrow error\n\t\t\t\t}\n\t\t\t})()\n\t\t\tconst most = budget.limits.conditionNodes\n\t\t\tif (node.weight > most) {\n\t\t\t\tthrow problemAt(\n\t\t\t\t\tat,\n\t\t\t\t\t`A condition has ${String(node.weight)} nodes and values. The most is ${String(most)}.`,\n\t\t\t\t)\n\t\t\t}\n\t\t\treturn node\n\t\t}\n\t\tconst children = (name: string) =>\n\t\t\tR.forEach(readList(input, name, at, budget), (item, index) =>\n\t\t\t\tchild({ input: item, at: Place.at(at, name, index) }, depth + 1),\n\t\t\t)\n\t\tconst tag = read(input, \"_tag\", at)\n\t\tswitch (tag) {\n\t\t\tcase \"True\":\n\t\t\t\treturn R.succeed(Nodes.always)\n\t\t\tcase \"False\":\n\t\t\t\treturn R.succeed(Nodes.never)\n\t\t\tcase \"Compare\": {\n\t\t\t\tconst op = readString(input, \"op\", at)\n\t\t\t\tif (!Nodes.isCompareOperator(op)) {\n\t\t\t\t\tthrow problemAt(\n\t\t\t\t\t\tPlace.at(at, \"op\"),\n\t\t\t\t\t\t`\"${op}\" is not a compare operator`,\n\t\t\t\t\t)\n\t\t\t\t}\n\t\t\t\treturn R.succeed(\n\t\t\t\t\tmake(\n\t\t\t\t\t\t() =>\n\t\t\t\t\t\t\tnew Nodes.Compare({\n\t\t\t\t\t\t\t\top,\n\t\t\t\t\t\t\t\tfield: readPath(input, \"field\", at, budget),\n\t\t\t\t\t\t\t\tvalue: decodeOperand(\n\t\t\t\t\t\t\t\t\tread(input, \"value\", at),\n\t\t\t\t\t\t\t\t\tPlace.at(at, \"value\"),\n\t\t\t\t\t\t\t\t\tbudget,\n\t\t\t\t\t\t\t\t),\n\t\t\t\t\t\t\t}),\n\t\t\t\t\t),\n\t\t\t\t)\n\t\t\t}\n\t\t\tcase \"In\":\n\t\t\t\treturn R.succeed(\n\t\t\t\t\tmake(\n\t\t\t\t\t\t() =>\n\t\t\t\t\t\t\tnew Nodes.In({\n\t\t\t\t\t\t\t\tfield: readPath(input, \"field\", at, budget),\n\t\t\t\t\t\t\t\tvalues: operands(\"values\"),\n\t\t\t\t\t\t\t}),\n\t\t\t\t\t),\n\t\t\t\t)\n\t\t\tcase \"Exists\":\n\t\t\t\treturn R.succeed(\n\t\t\t\t\tmake(\n\t\t\t\t\t\t() =>\n\t\t\t\t\t\t\tnew Nodes.Exists({ field: readPath(input, \"field\", at, budget) }),\n\t\t\t\t\t),\n\t\t\t\t)\n\t\t\tcase \"Under\":\n\t\t\t\treturn R.succeed(\n\t\t\t\t\tmake(\n\t\t\t\t\t\t() =>\n\t\t\t\t\t\t\tnew Nodes.Under({\n\t\t\t\t\t\t\t\tprefix: operands(\"prefix\"),\n\t\t\t\t\t\t\t}),\n\t\t\t\t\t),\n\t\t\t\t)\n\t\t\tcase \"And\":\n\t\t\t\treturn R.map(children(\"conditions\"), (conditions) =>\n\t\t\t\t\tmake(() => new Nodes.And(conditions)),\n\t\t\t\t)\n\t\t\tcase \"Or\":\n\t\t\t\treturn R.map(children(\"conditions\"), (conditions) =>\n\t\t\t\t\tmake(() => new Nodes.Or(conditions)),\n\t\t\t\t)\n\t\t\tcase \"Not\":\n\t\t\t\treturn R.map(\n\t\t\t\t\tchild(\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tinput: read(input, \"condition\", at),\n\t\t\t\t\t\t\tat: Place.at(at, \"condition\"),\n\t\t\t\t\t\t},\n\t\t\t\t\t\tdepth + 1,\n\t\t\t\t\t),\n\t\t\t\t\t(condition) => make(() => new Nodes.Not(condition)),\n\t\t\t\t)\n\t\t\tdefault:\n\t\t\t\tthrow problemAt(Place.at(at, \"_tag\"), \"The node has no known tag\")\n\t\t}\n\t})\n}\n\n/**\n * Decodes the JSON form of a condition at a path, with the budget of a larger\n * decode. It throws a {@link Problem}.\n */\nexport const decodeAt = (\n\tinput: unknown,\n\tat: Place.Place | undefined,\n\tbudget: Budget,\n): Node => decoder(budget)({ input, at })\n\n/** Decodes the JSON form of a condition, with the default limits. */\nexport const decode = (input: unknown): Result.Result<Node, Problem> => {\n\ttry {\n\t\treturn Result.succeed(\n\t\t\tdecoder({ used: 0, limits: Limits.defaults })({ input, at: undefined }),\n\t\t)\n\t} catch (error) {\n\t\tif (error instanceof Problem) {\n\t\t\treturn Result.fail(error)\n\t\t}\n\t\tthrow error\n\t}\n}\n"],"mappings":"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"}
@@ -1,8 +1,8 @@
1
1
  import { ceilings, fieldPathParts, patternParts } from "./limits.js";
2
2
  import { keyFits } from "./catalog.js";
3
3
  import * as Arr from "effect/Array";
4
- import * as Result from "effect/Result";
5
4
  import * as Option from "effect/Option";
5
+ import * as Result from "effect/Result";
6
6
  // src/internal/patterns.ts
7
7
  /**
8
8
  * Field patterns, as in `settings.*`, `secret*` or `**.secret`. A pattern is
@@ -136,11 +136,11 @@ const decodeMeta = (rule, domain, budget) => {
136
136
  value,
137
137
  schema: domain.meta
138
138
  };
139
- })), (found) => found === void 0 ? Effect.void : Effect.mapError(Schema.decodeUnknownEffect(metaCodec(found.schema))(found.value), (error) => new Problem({
139
+ })), (found) => found === void 0 ? Effect.void : Schema.decodeUnknownEffect(metaCodec(found.schema))(found.value).pipe(Effect.mapError((error) => new Problem({
140
140
  path: pathOf(at$4),
141
141
  message: error.message,
142
142
  issue: error.issue
143
- })));
143
+ })), Effect.catchDefect((defect) => defect instanceof RangeError ? Effect.fail(problemAt(at$4, "The meta of the rule has too many levels for the Schema of meta")) : Effect.die(defect))));
144
144
  };
145
145
  const decodeRule = (rule, meta, budget, domain) => {
146
146
  const { input, place } = rule;
@@ -1 +1 @@
1
- {"version":3,"file":"policy-json.js","names":["PolicyNodes.rulesOf","Json.toJson","Trampoline.run","Json.readList","at","Json.problemAt","Place.at","read","Trampoline.adaptive","Json.read","Json.Problem","Place.pathOf","Json.decodeAt","Json.readString","Rule.make","PolicyNodes.isAlgorithm","Limits.LimitExceeded","PolicyNodes.Rules","PolicyNodes","PolicyNodes.Ceiling"],"sources":["../../src/internal/policy-json.ts"],"sourcesContent":["/**\n * The JSON form of a policy tree. The decoder is a plain walk, as the decoder\n * of a condition is, and the two share one budget of `jsonNodes` objects and\n * items. It makes each node with its constructor, so the limits of the nodes\n * hold for a decoded tree too. The binding of the rules to the domain comes\n * after, in the Schema of the policy.\n */\nimport * as Arr from \"effect/Array\"\nimport * as Effect from \"effect/Effect\"\nimport * as Schema from \"effect/Schema\"\nimport type * as Domain from \"../Domain.ts\"\nimport * as Rule from \"./rule.ts\"\nimport * as Json from \"./json.ts\"\nimport * as Limits from \"./limits.ts\"\nimport * as Place from \"./place.ts\"\nimport * as PolicyNodes from \"./policy-nodes.ts\"\nimport * as Trampoline from \"./trampoline.ts\"\n\ntype Node = PolicyNodes.Node\n\n/**\n * The codec of the `meta` Schema of a domain to and from JSON. The JSON form\n * of a policy must stay the same through `JSON.stringify` and `JSON.parse`, so\n * a `Date` in `meta`, for one, becomes a string in it, and a string again\n * when it is decoded.\n */\nconst metaCodec = (meta: Schema.Top): Schema.Codec<unknown, unknown> =>\n\tSchema.toCodecJson(meta) as unknown as Schema.Codec<unknown, unknown>\n\n/** Gives the JSON form of the `meta` of a rule, or `undefined` for none. */\nexport type MetaJson = (rule: Rule.Rule) => unknown\n\n/**\n * Gives the JSON form of the `meta` of each rule at once, with the `meta`\n * Schema of a domain. `JSON.stringify` needs it at once, so the Schema must\n * not need an async step there.\n */\nexport const syncMeta =\n\t(meta: Schema.Top | undefined): MetaJson =>\n\t(rule) =>\n\t\trule.meta === undefined || meta === undefined\n\t\t\t? undefined\n\t\t\t: Schema.encodeUnknownSync(metaCodec(meta))(rule.meta)\n\n/** The JSON form of the condition of each rule, by the rule. */\ntype WhenJson = (rule: Rule.Rule) => unknown\n\n/** Gives the JSON form of a rule. The domain of a rule is not in it. */\nconst ruleJson = (\n\trule: Rule.Rule,\n\tmetaJson: MetaJson,\n\twhenJson: WhenJson,\n): unknown => {\n\tconst meta = metaJson(rule)\n\treturn {\n\t\t_tag: rule._tag,\n\t\tactions: rule.actions,\n\t\tresources: rule.resources,\n\t\t...(rule.when === undefined ? {} : { when: whenJson(rule) }),\n\t\t...(rule.fields === undefined ? {} : { fields: rule.fields }),\n\t\t...(rule.reason === undefined ? {} : { reason: rule.reason }),\n\t\t...(rule.id === undefined ? {} : { id: rule.id }),\n\t\t// The `meta` of a rule is a decoded value. Its JSON form comes from the\n\t\t// `meta` Schema of the domain.\n\t\t...(meta === undefined ? {} : { meta }),\n\t}\n}\n\n/**\n * Gives the JSON form of a policy tree with no predicate. A condition with a\n * predicate has no JSON form, so it throws an error. The conditions are\n * written first, from a flat list of the rules, so the walk does not go down\n * the levels of the tree and of a condition at once.\n */\nexport const toJson = (node: Node, meta: MetaJson): unknown => {\n\t// A native map, because it must find a rule by the rule itself: two rules\n\t// with the same data are `Equal`.\n\tconst whens = new Map<Rule.Rule, unknown>(\n\t\tArr.map(PolicyNodes.rulesOf(node), (rule) => [\n\t\t\trule,\n\t\t\trule.when === undefined ? undefined : Json.toJson(rule.when as never),\n\t\t]),\n\t)\n\t// The walk of the tree takes the steps of a trampoline, so a deep tree\n\t// does not fill the stack.\n\treturn Trampoline.run(node.depth, (R) => {\n\t\tconst tree = (current: Node): Trampoline.Step<unknown> => {\n\t\t\tswitch (current._tag) {\n\t\t\t\tcase \"Rules\":\n\t\t\t\t\treturn R.succeed({\n\t\t\t\t\t\t_tag: \"Rules\",\n\t\t\t\t\t\talgorithm: current.algorithm,\n\t\t\t\t\t\trules: Arr.map(current.rules, (rule) =>\n\t\t\t\t\t\t\truleJson(rule, meta, (found) => whens.get(found)),\n\t\t\t\t\t\t),\n\t\t\t\t\t})\n\t\t\t\tcase \"All\":\n\t\t\t\tcase \"Any\":\n\t\t\t\tcase \"FirstOf\": {\n\t\t\t\t\tconst tag = current._tag\n\t\t\t\t\treturn R.map(\n\t\t\t\t\t\tR.forEach(current.policies, (child) =>\n\t\t\t\t\t\t\tR.suspend(() => tree(child)),\n\t\t\t\t\t\t),\n\t\t\t\t\t\t(policies) => ({ _tag: tag, policies }),\n\t\t\t\t\t)\n\t\t\t\t}\n\t\t\t\tcase \"Ceiling\":\n\t\t\t\t\treturn R.flatMap(\n\t\t\t\t\t\tR.suspend(() => tree(current.cap)),\n\t\t\t\t\t\t(cap) =>\n\t\t\t\t\t\t\tR.map(\n\t\t\t\t\t\t\t\tR.suspend(() => tree(current.policy)),\n\t\t\t\t\t\t\t\t(policy) => ({ _tag: \"Ceiling\", cap, policy }),\n\t\t\t\t\t\t\t),\n\t\t\t\t\t)\n\t\t\t}\n\t\t}\n\t\treturn tree(node)\n\t})\n}\n\n/**\n * Gives the JSON form of a policy tree as an Effect. The `meta` Schema of the\n * domain may need an async step, so each `meta` is encoded as an Effect.\n */\nexport const encode = (\n\tnode: Node,\n\tmeta: Schema.Top | undefined,\n): Effect.Effect<unknown, Schema.SchemaError> =>\n\tEffect.gen(function* () {\n\t\tconst rules = Arr.filter(\n\t\t\tPolicyNodes.rulesOf(node),\n\t\t\t(rule) => rule.meta !== undefined,\n\t\t)\n\t\tconst encoded =\n\t\t\tmeta === undefined\n\t\t\t\t? []\n\t\t\t\t: yield* Effect.forEach(rules, (rule) =>\n\t\t\t\t\t\tEffect.map(\n\t\t\t\t\t\t\tSchema.encodeUnknownEffect(metaCodec(meta))(rule.meta),\n\t\t\t\t\t\t\t(json) => [rule, json] as const,\n\t\t\t\t\t\t),\n\t\t\t\t\t)\n\t\t// A native map, because it must find a rule by the rule itself: two\n\t\t// rules with the same data are `Equal`, and each keeps its own form.\n\t\tconst forms = new Map<Rule.Rule, unknown>(encoded)\n\t\treturn toJson(node, (rule) => forms.get(rule))\n\t})\n\n/** Reads a list of strings. */\nconst readStrings = (\n\tinput: object,\n\tname: string,\n\tat: Place.Place | undefined,\n\tbudget: Json.Budget,\n): ReadonlyArray<string> =>\n\tArr.map(Json.readList(input, name, at, budget), (value, index) => {\n\t\tif (typeof value !== \"string\") {\n\t\t\tthrow Json.problemAt(\n\t\t\t\tPlace.at(at, name, index),\n\t\t\t\t\"The item must be a string\",\n\t\t\t)\n\t\t}\n\t\treturn value\n\t})\n\n/**\n * Reads a field that a rule may leave out. A field that the input has only\n * from the prototype of a class is not plain data, so it is a problem: a rule\n * that loses its condition would allow more.\n */\nconst readOptional = <A>(\n\tinput: object,\n\tname: string,\n\tat: Place.Place | undefined,\n\tread: () => A,\n): A | undefined => {\n\tif (Object.hasOwn(input, name)) {\n\t\treturn read()\n\t}\n\t// A read of the field throws a problem when the field is not data.\n\tJson.read(input, name, at)\n\treturn undefined\n}\n\n/** A value of the input to count, with its place. */\ninterface Part {\n\treadonly value: unknown\n\treadonly at: Place.Place | undefined\n}\n\n/**\n * Counts the objects and items of a value, as the other parts count. It is a\n * walk of the trampoline, so a deep value does not fill the stack.\n */\nconst spendValue = (budget: Json.Budget): ((part: Part) => void) =>\n\tTrampoline.adaptive<Part, void>((R, child) => (part, depth) => {\n\t\tconst { value, at } = part\n\t\tif (typeof value !== \"object\" || value === null) {\n\t\t\treturn R.succeed(undefined)\n\t\t}\n\t\t// The length of an array counts before its items are read, so a sparse\n\t\t// array with a very large length stops at once.\n\t\tconst list = Array.isArray(value)\n\t\tif (list) {\n\t\t\tJson.spend(budget, 1 + value.length, at)\n\t\t}\n\t\tconst names = list\n\t\t\t? Array.from({ length: value.length }, (_, index) => String(index))\n\t\t\t: Object.keys(value)\n\t\tif (!list) {\n\t\t\tJson.spend(budget, 1 + names.length, at)\n\t\t}\n\t\treturn R.map(\n\t\t\tR.forEach(names, (name) =>\n\t\t\t\tchild(\n\t\t\t\t\t{ value: Json.read(value, name, at), at: Place.at(at, name) },\n\t\t\t\t\tdepth + 1,\n\t\t\t\t),\n\t\t\t),\n\t\t\t() => undefined,\n\t\t)\n\t})\n\n/**\n * A rule in the input, with its place, to decode after the tree. The place\n * shares the path of its parent: see `Place`.\n */\ninterface RuleInput {\n\treadonly input: unknown\n\treadonly place: Place.Place\n}\n\n/**\n * The shape of a tree, with the place of each rule in a flat list. `size`\n * counts the nodes and the rules, as the nodes do, so a shared input stops\n * at `policyNodes` before the decoder reads all of it.\n */\ntype Plan =\n\t| {\n\t\t\treadonly _tag: \"Rules\"\n\t\t\treadonly place: Place.Place | undefined\n\t\t\treadonly size: number\n\t\t\treadonly algorithm: PolicyNodes.Algorithm\n\t\t\treadonly rules: ReadonlyArray<number>\n\t }\n\t| {\n\t\t\treadonly _tag: \"All\" | \"Any\" | \"FirstOf\"\n\t\t\treadonly place: Place.Place | undefined\n\t\t\treadonly size: number\n\t\t\treadonly policies: ReadonlyArray<Plan>\n\t }\n\t| {\n\t\t\treadonly _tag: \"Ceiling\"\n\t\t\treadonly place: Place.Place | undefined\n\t\t\treadonly size: number\n\t\t\treadonly cap: Plan\n\t\t\treadonly policy: Plan\n\t }\n\n/**\n * Decodes the `meta` of a rule with the `meta` Schema of the domain, as an\n * Effect, since the Schema may need an async step. The Schema reads all of\n * the value, so its size counts in the budget.\n */\nconst decodeMeta = (\n\trule: RuleInput,\n\tdomain: Domain.Any,\n\tbudget: Json.Budget,\n): Effect.Effect<unknown, Json.Problem> => {\n\tconst { input, place } = rule\n\tconst at = Place.at(place, \"meta\")\n\treturn Effect.flatMap(\n\t\tattempt(() =>\n\t\t\ttypeof input !== \"object\" || input === null\n\t\t\t\t? undefined\n\t\t\t\t: readOptional(input, \"meta\", place, () => {\n\t\t\t\t\t\tconst value = Json.read(input, \"meta\", place)\n\t\t\t\t\t\tif (domain.meta === undefined) {\n\t\t\t\t\t\t\tthrow Json.problemAt(at, \"The domain has no Schema for meta\")\n\t\t\t\t\t\t}\n\t\t\t\t\t\tspendValue(budget)({ value, at })\n\t\t\t\t\t\treturn { value, schema: domain.meta }\n\t\t\t\t\t}),\n\t\t),\n\t\t(found) =>\n\t\t\tfound === undefined\n\t\t\t\t? Effect.void\n\t\t\t\t: Effect.mapError(\n\t\t\t\t\t\tSchema.decodeUnknownEffect(metaCodec(found.schema))(found.value),\n\t\t\t\t\t\t(error) =>\n\t\t\t\t\t\t\tnew Json.Problem({\n\t\t\t\t\t\t\t\tpath: Place.pathOf(at),\n\t\t\t\t\t\t\t\tmessage: error.message,\n\t\t\t\t\t\t\t\tissue: error.issue,\n\t\t\t\t\t\t\t}),\n\t\t\t\t\t),\n\t)\n}\n\nconst decodeRule = (\n\trule: RuleInput,\n\tmeta: unknown,\n\tbudget: Json.Budget,\n\tdomain: Domain.Any,\n): Rule.Rule => {\n\tconst { input, place } = rule\n\tif (typeof input !== \"object\" || input === null) {\n\t\tthrow Json.problemAt(place, \"A rule must be an object\")\n\t}\n\tJson.spend(budget, 1, place)\n\tconst tag = Json.read(input, \"_tag\", place)\n\tif (tag !== \"Allow\" && tag !== \"Deny\") {\n\t\tthrow Json.problemAt(\n\t\t\tPlace.at(place, \"_tag\"),\n\t\t\t'A rule must be \"Allow\" or \"Deny\"',\n\t\t)\n\t}\n\tconst when = readOptional(input, \"when\", place, () =>\n\t\tJson.decodeAt(\n\t\t\tJson.read(input, \"when\", place),\n\t\t\tPlace.at(place, \"when\"),\n\t\t\tbudget,\n\t\t),\n\t)\n\tconst fields = readOptional(input, \"fields\", place, () =>\n\t\treadStrings(input, \"fields\", place, budget),\n\t)\n\tconst reason = readOptional(input, \"reason\", place, () =>\n\t\tJson.readString(input, \"reason\", place),\n\t)\n\tconst id = readOptional(input, \"id\", place, () =>\n\t\tJson.readString(input, \"id\", place),\n\t)\n\t// Each rule gets the domain of the Schema, as a rule from the builder of\n\t// the domain has it.\n\treturn Rule.make({\n\t\t_tag: tag,\n\t\tactions: readStrings(input, \"actions\", place, budget),\n\t\tresources: readStrings(input, \"resources\", place, budget),\n\t\t...(when === undefined ? {} : { when: when as never }),\n\t\t...(fields === undefined ? {} : { fields }),\n\t\t...(reason === undefined ? {} : { reason }),\n\t\t...(id === undefined ? {} : { id }),\n\t\t...(meta === undefined ? {} : { meta }),\n\t\tdomain,\n\t})\n}\n\n/** A node of the input of a tree, with its place. */\ninterface TreePart {\n\treadonly input: unknown\n\treadonly at: Place.Place | undefined\n}\n\n/**\n * Reads the shape of a tree, and puts each rule in `rules`. The rules are\n * decoded after the walk: a condition has levels of its own, and a decode\n * inside the walk would go down the levels of both. The walk is a walk of\n * the trampoline, so a deep tree does not fill the stack.\n */\nconst readTree = (\n\tbudget: Json.Budget,\n\t// The rules of one decode. The list belongs to that decode.\n\trules: Array<RuleInput>,\n): ((part: TreePart) => Plan) => {\n\t// The nodes and rules of the tree that the walk has read. A tree with more\n\t// of them than `policyNodes` does not load, so the walk stops at the first\n\t// one past it, and it does not read the rest of a very large input. The\n\t// count belongs to one decode.\n\tconst count = { nodes: 0 }\n\tconst counted = (more: number, at: Place.Place | undefined): void => {\n\t\tcount.nodes += more\n\t\tconst most = budget.limits.policyNodes\n\t\tif (count.nodes > most) {\n\t\t\tthrow Json.problemAt(\n\t\t\t\tat,\n\t\t\t\t`A policy tree has more than ${String(most)} nodes. The most is ${String(most)}.`,\n\t\t\t)\n\t\t}\n\t}\n\treturn Trampoline.adaptive<TreePart, Plan>((R, child) => (part, depth) => {\n\t\tconst { input, at } = part\n\t\tif (typeof input !== \"object\" || input === null) {\n\t\t\tthrow Json.problemAt(at, \"A node of a policy must be an object\")\n\t\t}\n\t\tJson.spend(budget, 1, at)\n\t\tcounted(1, at)\n\t\tconst below = (value: unknown, ...parts: ReadonlyArray<PropertyKey>) =>\n\t\t\tchild({ input: value, at: Place.at(at, ...parts) }, depth + 1)\n\t\tconst tag = Json.read(input, \"_tag\", at)\n\t\tswitch (tag) {\n\t\t\tcase \"Rules\": {\n\t\t\t\tconst algorithm = Json.readString(input, \"algorithm\", at)\n\t\t\t\tif (!PolicyNodes.isAlgorithm(algorithm)) {\n\t\t\t\t\tthrow Json.problemAt(\n\t\t\t\t\t\tPlace.at(at, \"algorithm\"),\n\t\t\t\t\t\t`\"${algorithm}\" is not a combining algorithm`,\n\t\t\t\t\t)\n\t\t\t\t}\n\t\t\t\tconst list = Json.readList(input, \"rules\", at, budget)\n\t\t\t\tcounted(list.length, at)\n\t\t\t\tconst found = Arr.map(\n\t\t\t\t\tlist,\n\t\t\t\t\t(rule, index) =>\n\t\t\t\t\t\trules.push({\n\t\t\t\t\t\t\tinput: rule,\n\t\t\t\t\t\t\tplace: Place.at(at, \"rules\", index),\n\t\t\t\t\t\t}) - 1,\n\t\t\t\t)\n\t\t\t\treturn R.succeed(\n\t\t\t\t\tsized(budget, {\n\t\t\t\t\t\t_tag: \"Rules\",\n\t\t\t\t\t\tplace: at,\n\t\t\t\t\t\tsize: 1 + found.length,\n\t\t\t\t\t\talgorithm,\n\t\t\t\t\t\trules: found,\n\t\t\t\t\t}),\n\t\t\t\t)\n\t\t\t}\n\t\t\tcase \"All\":\n\t\t\tcase \"Any\":\n\t\t\tcase \"FirstOf\":\n\t\t\t\treturn R.map(\n\t\t\t\t\tR.forEach(\n\t\t\t\t\t\tJson.readList(input, \"policies\", at, budget),\n\t\t\t\t\t\t(value, index) => below(value, \"policies\", index),\n\t\t\t\t\t),\n\t\t\t\t\t(policies) =>\n\t\t\t\t\t\tsized(budget, {\n\t\t\t\t\t\t\t_tag: tag,\n\t\t\t\t\t\t\tplace: at,\n\t\t\t\t\t\t\tsize: Arr.reduce(policies, 1, (total, plan) => total + plan.size),\n\t\t\t\t\t\t\tpolicies,\n\t\t\t\t\t\t}),\n\t\t\t\t)\n\t\t\tcase \"Ceiling\": {\n\t\t\t\tconst cap = Json.read(input, \"cap\", at)\n\t\t\t\tconst policy = Json.read(input, \"policy\", at)\n\t\t\t\treturn R.flatMap(below(cap, \"cap\"), (capPlan) =>\n\t\t\t\t\tR.map(below(policy, \"policy\"), (policyPlan) =>\n\t\t\t\t\t\tsized(budget, {\n\t\t\t\t\t\t\t_tag: \"Ceiling\",\n\t\t\t\t\t\t\tplace: at,\n\t\t\t\t\t\t\tsize: 1 + capPlan.size + policyPlan.size,\n\t\t\t\t\t\t\tcap: capPlan,\n\t\t\t\t\t\t\tpolicy: policyPlan,\n\t\t\t\t\t\t}),\n\t\t\t\t\t),\n\t\t\t\t)\n\t\t\t}\n\t\t\tdefault:\n\t\t\t\tthrow Json.problemAt(Place.at(at, \"_tag\"), \"The node has no known tag\")\n\t\t}\n\t})\n}\n\n/** Stops a plan past `policyNodes`, with the message of the nodes. */\nconst sized = (budget: Json.Budget, plan: Plan): Plan => {\n\tconst most = budget.limits.policyNodes\n\tif (plan.size > most) {\n\t\tthrow Json.problemAt(\n\t\t\tplan.place,\n\t\t\t`A policy tree has ${String(plan.size)} nodes. The most is ${String(most)}.`,\n\t\t)\n\t}\n\treturn plan\n}\n\n/**\n * Makes the nodes of a plan, with the decoded rules. It is a walk of the\n * trampoline, so a deep plan does not fill the stack.\n */\nconst build = (\n\tbudget: Json.Budget,\n\trules: ReadonlyArray<Rule.Rule>,\n): ((plan: Plan) => Node) =>\n\tTrampoline.adaptive<Plan, Node>((R, child) => (plan, depth) => {\n\t\t// A node past a limit is a problem at its place. The constructor checks\n\t\t// the ceilings, and the decoder checks the limits of the domain, with\n\t\t// the nodes and values of the conditions.\n\t\tconst make = (made: () => Node): Node => {\n\t\t\tconst node = (() => {\n\t\t\t\ttry {\n\t\t\t\t\treturn made()\n\t\t\t\t} catch (error) {\n\t\t\t\t\tif (error instanceof Limits.LimitExceeded) {\n\t\t\t\t\t\tthrow Json.problemAt(plan.place, error.message)\n\t\t\t\t\t}\n\t\t\t\t\tthrow error\n\t\t\t\t}\n\t\t\t})()\n\t\t\tconst most = budget.limits.policyNodes\n\t\t\tif (node.size > most) {\n\t\t\t\tthrow Json.problemAt(\n\t\t\t\t\tplan.place,\n\t\t\t\t\t`A policy tree has ${String(node.size)} nodes. The most is ${String(most)}.`,\n\t\t\t\t)\n\t\t\t}\n\t\t\treturn node\n\t\t}\n\t\tswitch (plan._tag) {\n\t\t\tcase \"Rules\":\n\t\t\t\treturn R.succeed(\n\t\t\t\t\tmake(\n\t\t\t\t\t\t() =>\n\t\t\t\t\t\t\tnew PolicyNodes.Rules({\n\t\t\t\t\t\t\t\talgorithm: plan.algorithm,\n\t\t\t\t\t\t\t\trules: Arr.map(plan.rules, (index) => rules[index]!),\n\t\t\t\t\t\t\t}),\n\t\t\t\t\t),\n\t\t\t\t)\n\t\t\tcase \"All\":\n\t\t\tcase \"Any\":\n\t\t\tcase \"FirstOf\": {\n\t\t\t\tconst tag = plan._tag\n\t\t\t\treturn R.map(\n\t\t\t\t\tR.forEach(plan.policies, (inner) => child(inner, depth + 1)),\n\t\t\t\t\t(policies) => make(() => new PolicyNodes[tag](policies)),\n\t\t\t\t)\n\t\t\t}\n\t\t\tcase \"Ceiling\":\n\t\t\t\treturn R.flatMap(child(plan.cap, depth + 1), (cap) =>\n\t\t\t\t\tR.map(child(plan.policy, depth + 1), (policy) =>\n\t\t\t\t\t\tmake(() => new PolicyNodes.Ceiling({ cap, policy })),\n\t\t\t\t\t),\n\t\t\t\t)\n\t\t}\n\t})\n\n/** Runs a step that may throw a problem, and puts the problem in the error. */\nconst attempt = <A>(step: () => A): Effect.Effect<A, Json.Problem> =>\n\tEffect.try({\n\t\ttry: step,\n\t\tcatch: (error) => {\n\t\t\tif (error instanceof Json.Problem) {\n\t\t\t\treturn error\n\t\t\t}\n\t\t\tthrow error\n\t\t},\n\t})\n\n/**\n * Decodes the JSON form of a policy tree for a domain. It reads the tree\n * first, then the `meta` of each rule as an Effect, then the rules, and it\n * makes the nodes last.\n */\nexport const decode = (\n\tinput: unknown,\n\tdomain: Domain.Any,\n): Effect.Effect<Node, Json.Problem> =>\n\tEffect.gen(function* () {\n\t\tconst budget: Json.Budget = { used: 0, limits: domain.limits }\n\t\t// The rules of this decode. The list belongs to this decode.\n\t\tconst inputs: Array<RuleInput> = []\n\t\tconst plan = yield* attempt(() =>\n\t\t\treadTree(budget, inputs)({ input, at: undefined }),\n\t\t)\n\t\tconst metas = yield* Effect.forEach(inputs, (rule) =>\n\t\t\tdecodeMeta(rule, domain, budget),\n\t\t)\n\t\tconst rules = yield* attempt(() =>\n\t\t\tArr.map(inputs, (rule, index) =>\n\t\t\t\tdecodeRule(rule, metas[index], budget, domain),\n\t\t\t),\n\t\t)\n\t\treturn yield* attempt(() => build(budget, rules)(plan))\n\t})\n"],"mappings":"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"}
1
+ {"version":3,"file":"policy-json.js","names":["PolicyNodes.rulesOf","Json.toJson","Trampoline.run","Json.readList","at","Json.problemAt","Place.at","read","Trampoline.adaptive","Json.read","Json.Problem","Place.pathOf","Json.decodeAt","Json.readString","Rule.make","PolicyNodes.isAlgorithm","Limits.LimitExceeded","PolicyNodes.Rules","PolicyNodes","PolicyNodes.Ceiling"],"sources":["../../src/internal/policy-json.ts"],"sourcesContent":["/**\n * The JSON form of a policy tree. The decoder is a plain walk, as the decoder\n * of a condition is, and the two share one budget of `jsonNodes` objects and\n * items. It makes each node with its constructor, so the limits of the nodes\n * hold for a decoded tree too. The binding of the rules to the domain comes\n * after, in the Schema of the policy.\n */\nimport * as Arr from \"effect/Array\"\nimport * as Effect from \"effect/Effect\"\nimport * as Schema from \"effect/Schema\"\nimport type * as Domain from \"../Domain.ts\"\nimport * as Rule from \"./rule.ts\"\nimport * as Json from \"./json.ts\"\nimport * as Limits from \"./limits.ts\"\nimport * as Place from \"./place.ts\"\nimport * as PolicyNodes from \"./policy-nodes.ts\"\nimport * as Trampoline from \"./trampoline.ts\"\n\ntype Node = PolicyNodes.Node\n\n/**\n * The codec of the `meta` Schema of a domain to and from JSON. The JSON form\n * of a policy must stay the same through `JSON.stringify` and `JSON.parse`, so\n * a `Date` in `meta`, for one, becomes a string in it, and a string again\n * when it is decoded.\n */\nconst metaCodec = (meta: Schema.Top): Schema.Codec<unknown, unknown> =>\n\tSchema.toCodecJson(meta) as unknown as Schema.Codec<unknown, unknown>\n\n/** Gives the JSON form of the `meta` of a rule, or `undefined` for none. */\nexport type MetaJson = (rule: Rule.Rule) => unknown\n\n/**\n * Gives the JSON form of the `meta` of each rule at once, with the `meta`\n * Schema of a domain. `JSON.stringify` needs it at once, so the Schema must\n * not need an async step there.\n */\nexport const syncMeta =\n\t(meta: Schema.Top | undefined): MetaJson =>\n\t(rule) =>\n\t\trule.meta === undefined || meta === undefined\n\t\t\t? undefined\n\t\t\t: Schema.encodeUnknownSync(metaCodec(meta))(rule.meta)\n\n/** The JSON form of the condition of each rule, by the rule. */\ntype WhenJson = (rule: Rule.Rule) => unknown\n\n/** Gives the JSON form of a rule. The domain of a rule is not in it. */\nconst ruleJson = (\n\trule: Rule.Rule,\n\tmetaJson: MetaJson,\n\twhenJson: WhenJson,\n): unknown => {\n\tconst meta = metaJson(rule)\n\treturn {\n\t\t_tag: rule._tag,\n\t\tactions: rule.actions,\n\t\tresources: rule.resources,\n\t\t...(rule.when === undefined ? {} : { when: whenJson(rule) }),\n\t\t...(rule.fields === undefined ? {} : { fields: rule.fields }),\n\t\t...(rule.reason === undefined ? {} : { reason: rule.reason }),\n\t\t...(rule.id === undefined ? {} : { id: rule.id }),\n\t\t// The `meta` of a rule is a decoded value. Its JSON form comes from the\n\t\t// `meta` Schema of the domain.\n\t\t...(meta === undefined ? {} : { meta }),\n\t}\n}\n\n/**\n * Gives the JSON form of a policy tree with no predicate. A condition with a\n * predicate has no JSON form, so it throws an error. The conditions are\n * written first, from a flat list of the rules, so the walk does not go down\n * the levels of the tree and of a condition at once.\n */\nexport const toJson = (node: Node, meta: MetaJson): unknown => {\n\t// A native map, because it must find a rule by the rule itself: two rules\n\t// with the same data are `Equal`.\n\tconst whens = new Map<Rule.Rule, unknown>(\n\t\tArr.map(PolicyNodes.rulesOf(node), (rule) => [\n\t\t\trule,\n\t\t\trule.when === undefined ? undefined : Json.toJson(rule.when as never),\n\t\t]),\n\t)\n\t// The walk of the tree takes the steps of a trampoline, so a deep tree\n\t// does not fill the stack.\n\treturn Trampoline.run(node.depth, (R) => {\n\t\tconst tree = (current: Node): Trampoline.Step<unknown> => {\n\t\t\tswitch (current._tag) {\n\t\t\t\tcase \"Rules\":\n\t\t\t\t\treturn R.succeed({\n\t\t\t\t\t\t_tag: \"Rules\",\n\t\t\t\t\t\talgorithm: current.algorithm,\n\t\t\t\t\t\trules: Arr.map(current.rules, (rule) =>\n\t\t\t\t\t\t\truleJson(rule, meta, (found) => whens.get(found)),\n\t\t\t\t\t\t),\n\t\t\t\t\t})\n\t\t\t\tcase \"All\":\n\t\t\t\tcase \"Any\":\n\t\t\t\tcase \"FirstOf\": {\n\t\t\t\t\tconst tag = current._tag\n\t\t\t\t\treturn R.map(\n\t\t\t\t\t\tR.forEach(current.policies, (child) =>\n\t\t\t\t\t\t\tR.suspend(() => tree(child)),\n\t\t\t\t\t\t),\n\t\t\t\t\t\t(policies) => ({ _tag: tag, policies }),\n\t\t\t\t\t)\n\t\t\t\t}\n\t\t\t\tcase \"Ceiling\":\n\t\t\t\t\treturn R.flatMap(\n\t\t\t\t\t\tR.suspend(() => tree(current.cap)),\n\t\t\t\t\t\t(cap) =>\n\t\t\t\t\t\t\tR.map(\n\t\t\t\t\t\t\t\tR.suspend(() => tree(current.policy)),\n\t\t\t\t\t\t\t\t(policy) => ({ _tag: \"Ceiling\", cap, policy }),\n\t\t\t\t\t\t\t),\n\t\t\t\t\t)\n\t\t\t}\n\t\t}\n\t\treturn tree(node)\n\t})\n}\n\n/**\n * Gives the JSON form of a policy tree as an Effect. The `meta` Schema of the\n * domain may need an async step, so each `meta` is encoded as an Effect.\n */\nexport const encode = (\n\tnode: Node,\n\tmeta: Schema.Top | undefined,\n): Effect.Effect<unknown, Schema.SchemaError> =>\n\tEffect.gen(function* () {\n\t\tconst rules = Arr.filter(\n\t\t\tPolicyNodes.rulesOf(node),\n\t\t\t(rule) => rule.meta !== undefined,\n\t\t)\n\t\tconst encoded =\n\t\t\tmeta === undefined\n\t\t\t\t? []\n\t\t\t\t: yield* Effect.forEach(rules, (rule) =>\n\t\t\t\t\t\tEffect.map(\n\t\t\t\t\t\t\tSchema.encodeUnknownEffect(metaCodec(meta))(rule.meta),\n\t\t\t\t\t\t\t(json) => [rule, json] as const,\n\t\t\t\t\t\t),\n\t\t\t\t\t)\n\t\t// A native map, because it must find a rule by the rule itself: two\n\t\t// rules with the same data are `Equal`, and each keeps its own form.\n\t\tconst forms = new Map<Rule.Rule, unknown>(encoded)\n\t\treturn toJson(node, (rule) => forms.get(rule))\n\t})\n\n/** Reads a list of strings. */\nconst readStrings = (\n\tinput: object,\n\tname: string,\n\tat: Place.Place | undefined,\n\tbudget: Json.Budget,\n): ReadonlyArray<string> =>\n\tArr.map(Json.readList(input, name, at, budget), (value, index) => {\n\t\tif (typeof value !== \"string\") {\n\t\t\tthrow Json.problemAt(\n\t\t\t\tPlace.at(at, name, index),\n\t\t\t\t\"The item must be a string\",\n\t\t\t)\n\t\t}\n\t\treturn value\n\t})\n\n/**\n * Reads a field that a rule may leave out. A field that the input has only\n * from the prototype of a class is not plain data, so it is a problem: a rule\n * that loses its condition would allow more.\n */\nconst readOptional = <A>(\n\tinput: object,\n\tname: string,\n\tat: Place.Place | undefined,\n\tread: () => A,\n): A | undefined => {\n\tif (Object.hasOwn(input, name)) {\n\t\treturn read()\n\t}\n\t// A read of the field throws a problem when the field is not data.\n\tJson.read(input, name, at)\n\treturn undefined\n}\n\n/** A value of the input to count, with its place. */\ninterface Part {\n\treadonly value: unknown\n\treadonly at: Place.Place | undefined\n}\n\n/**\n * Counts the objects and items of a value, as the other parts count. It is a\n * walk of the trampoline, so a deep value does not fill the stack.\n */\nconst spendValue = (budget: Json.Budget): ((part: Part) => void) =>\n\tTrampoline.adaptive<Part, void>((R, child) => (part, depth) => {\n\t\tconst { value, at } = part\n\t\tif (typeof value !== \"object\" || value === null) {\n\t\t\treturn R.succeed(undefined)\n\t\t}\n\t\t// The length of an array counts before its items are read, so a sparse\n\t\t// array with a very large length stops at once.\n\t\tconst list = Array.isArray(value)\n\t\tif (list) {\n\t\t\tJson.spend(budget, 1 + value.length, at)\n\t\t}\n\t\tconst names = list\n\t\t\t? Array.from({ length: value.length }, (_, index) => String(index))\n\t\t\t: Object.keys(value)\n\t\tif (!list) {\n\t\t\tJson.spend(budget, 1 + names.length, at)\n\t\t}\n\t\treturn R.map(\n\t\t\tR.forEach(names, (name) =>\n\t\t\t\tchild(\n\t\t\t\t\t{ value: Json.read(value, name, at), at: Place.at(at, name) },\n\t\t\t\t\tdepth + 1,\n\t\t\t\t),\n\t\t\t),\n\t\t\t() => undefined,\n\t\t)\n\t})\n\n/**\n * A rule in the input, with its place, to decode after the tree. The place\n * shares the path of its parent: see `Place`.\n */\ninterface RuleInput {\n\treadonly input: unknown\n\treadonly place: Place.Place\n}\n\n/**\n * The shape of a tree, with the place of each rule in a flat list. `size`\n * counts the nodes and the rules, as the nodes do, so a shared input stops\n * at `policyNodes` before the decoder reads all of it.\n */\ntype Plan =\n\t| {\n\t\t\treadonly _tag: \"Rules\"\n\t\t\treadonly place: Place.Place | undefined\n\t\t\treadonly size: number\n\t\t\treadonly algorithm: PolicyNodes.Algorithm\n\t\t\treadonly rules: ReadonlyArray<number>\n\t }\n\t| {\n\t\t\treadonly _tag: \"All\" | \"Any\" | \"FirstOf\"\n\t\t\treadonly place: Place.Place | undefined\n\t\t\treadonly size: number\n\t\t\treadonly policies: ReadonlyArray<Plan>\n\t }\n\t| {\n\t\t\treadonly _tag: \"Ceiling\"\n\t\t\treadonly place: Place.Place | undefined\n\t\t\treadonly size: number\n\t\t\treadonly cap: Plan\n\t\t\treadonly policy: Plan\n\t }\n\n/**\n * Decodes the `meta` of a rule with the `meta` Schema of the domain, as an\n * Effect, since the Schema may need an async step. The Schema reads all of\n * the value, so its size counts in the budget.\n */\nconst decodeMeta = (\n\trule: RuleInput,\n\tdomain: Domain.Any,\n\tbudget: Json.Budget,\n): Effect.Effect<unknown, Json.Problem> => {\n\tconst { input, place } = rule\n\tconst at = Place.at(place, \"meta\")\n\treturn Effect.flatMap(\n\t\tattempt(() =>\n\t\t\ttypeof input !== \"object\" || input === null\n\t\t\t\t? undefined\n\t\t\t\t: readOptional(input, \"meta\", place, () => {\n\t\t\t\t\t\tconst value = Json.read(input, \"meta\", place)\n\t\t\t\t\t\tif (domain.meta === undefined) {\n\t\t\t\t\t\t\tthrow Json.problemAt(at, \"The domain has no Schema for meta\")\n\t\t\t\t\t\t}\n\t\t\t\t\t\tspendValue(budget)({ value, at })\n\t\t\t\t\t\treturn { value, schema: domain.meta }\n\t\t\t\t\t}),\n\t\t),\n\t\t(found) =>\n\t\t\tfound === undefined\n\t\t\t\t? Effect.void\n\t\t\t\t: Schema.decodeUnknownEffect(metaCodec(found.schema))(found.value).pipe(\n\t\t\t\t\t\tEffect.mapError(\n\t\t\t\t\t\t\t(error) =>\n\t\t\t\t\t\t\t\tnew Json.Problem({\n\t\t\t\t\t\t\t\t\tpath: Place.pathOf(at),\n\t\t\t\t\t\t\t\t\tmessage: error.message,\n\t\t\t\t\t\t\t\t\tissue: error.issue,\n\t\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t),\n\t\t\t\t\t\t// The decoder of a Schema in Effect calls itself for each level of\n\t\t\t\t\t\t// the value, so a deep value that the budget takes, as 5,000 levels\n\t\t\t\t\t\t// of a recursive `meta` Schema, fills its stack. That is a defect\n\t\t\t\t\t\t// of the decoder, not of the policy, so it is a problem of the\n\t\t\t\t\t\t// input here, and the policy does not load.\n\t\t\t\t\t\tEffect.catchDefect((defect) =>\n\t\t\t\t\t\t\tdefect instanceof RangeError\n\t\t\t\t\t\t\t\t? Effect.fail(\n\t\t\t\t\t\t\t\t\t\tJson.problemAt(\n\t\t\t\t\t\t\t\t\t\t\tat,\n\t\t\t\t\t\t\t\t\t\t\t\"The meta of the rule has too many levels for the Schema of meta\",\n\t\t\t\t\t\t\t\t\t\t),\n\t\t\t\t\t\t\t\t\t)\n\t\t\t\t\t\t\t\t: Effect.die(defect),\n\t\t\t\t\t\t),\n\t\t\t\t\t),\n\t)\n}\n\nconst decodeRule = (\n\trule: RuleInput,\n\tmeta: unknown,\n\tbudget: Json.Budget,\n\tdomain: Domain.Any,\n): Rule.Rule => {\n\tconst { input, place } = rule\n\tif (typeof input !== \"object\" || input === null) {\n\t\tthrow Json.problemAt(place, \"A rule must be an object\")\n\t}\n\tJson.spend(budget, 1, place)\n\tconst tag = Json.read(input, \"_tag\", place)\n\tif (tag !== \"Allow\" && tag !== \"Deny\") {\n\t\tthrow Json.problemAt(\n\t\t\tPlace.at(place, \"_tag\"),\n\t\t\t'A rule must be \"Allow\" or \"Deny\"',\n\t\t)\n\t}\n\tconst when = readOptional(input, \"when\", place, () =>\n\t\tJson.decodeAt(\n\t\t\tJson.read(input, \"when\", place),\n\t\t\tPlace.at(place, \"when\"),\n\t\t\tbudget,\n\t\t),\n\t)\n\tconst fields = readOptional(input, \"fields\", place, () =>\n\t\treadStrings(input, \"fields\", place, budget),\n\t)\n\tconst reason = readOptional(input, \"reason\", place, () =>\n\t\tJson.readString(input, \"reason\", place),\n\t)\n\tconst id = readOptional(input, \"id\", place, () =>\n\t\tJson.readString(input, \"id\", place),\n\t)\n\t// Each rule gets the domain of the Schema, as a rule from the builder of\n\t// the domain has it.\n\treturn Rule.make({\n\t\t_tag: tag,\n\t\tactions: readStrings(input, \"actions\", place, budget),\n\t\tresources: readStrings(input, \"resources\", place, budget),\n\t\t...(when === undefined ? {} : { when: when as never }),\n\t\t...(fields === undefined ? {} : { fields }),\n\t\t...(reason === undefined ? {} : { reason }),\n\t\t...(id === undefined ? {} : { id }),\n\t\t...(meta === undefined ? {} : { meta }),\n\t\tdomain,\n\t})\n}\n\n/** A node of the input of a tree, with its place. */\ninterface TreePart {\n\treadonly input: unknown\n\treadonly at: Place.Place | undefined\n}\n\n/**\n * Reads the shape of a tree, and puts each rule in `rules`. The rules are\n * decoded after the walk: a condition has levels of its own, and a decode\n * inside the walk would go down the levels of both. The walk is a walk of\n * the trampoline, so a deep tree does not fill the stack.\n */\nconst readTree = (\n\tbudget: Json.Budget,\n\t// The rules of one decode. The list belongs to that decode.\n\trules: Array<RuleInput>,\n): ((part: TreePart) => Plan) => {\n\t// The nodes and rules of the tree that the walk has read. A tree with more\n\t// of them than `policyNodes` does not load, so the walk stops at the first\n\t// one past it, and it does not read the rest of a very large input. The\n\t// count belongs to one decode.\n\tconst count = { nodes: 0 }\n\tconst counted = (more: number, at: Place.Place | undefined): void => {\n\t\tcount.nodes += more\n\t\tconst most = budget.limits.policyNodes\n\t\tif (count.nodes > most) {\n\t\t\tthrow Json.problemAt(\n\t\t\t\tat,\n\t\t\t\t`A policy tree has more than ${String(most)} nodes. The most is ${String(most)}.`,\n\t\t\t)\n\t\t}\n\t}\n\treturn Trampoline.adaptive<TreePart, Plan>((R, child) => (part, depth) => {\n\t\tconst { input, at } = part\n\t\tif (typeof input !== \"object\" || input === null) {\n\t\t\tthrow Json.problemAt(at, \"A node of a policy must be an object\")\n\t\t}\n\t\tJson.spend(budget, 1, at)\n\t\tcounted(1, at)\n\t\tconst below = (value: unknown, ...parts: ReadonlyArray<PropertyKey>) =>\n\t\t\tchild({ input: value, at: Place.at(at, ...parts) }, depth + 1)\n\t\tconst tag = Json.read(input, \"_tag\", at)\n\t\tswitch (tag) {\n\t\t\tcase \"Rules\": {\n\t\t\t\tconst algorithm = Json.readString(input, \"algorithm\", at)\n\t\t\t\tif (!PolicyNodes.isAlgorithm(algorithm)) {\n\t\t\t\t\tthrow Json.problemAt(\n\t\t\t\t\t\tPlace.at(at, \"algorithm\"),\n\t\t\t\t\t\t`\"${algorithm}\" is not a combining algorithm`,\n\t\t\t\t\t)\n\t\t\t\t}\n\t\t\t\tconst list = Json.readList(input, \"rules\", at, budget)\n\t\t\t\tcounted(list.length, at)\n\t\t\t\tconst found = Arr.map(\n\t\t\t\t\tlist,\n\t\t\t\t\t(rule, index) =>\n\t\t\t\t\t\trules.push({\n\t\t\t\t\t\t\tinput: rule,\n\t\t\t\t\t\t\tplace: Place.at(at, \"rules\", index),\n\t\t\t\t\t\t}) - 1,\n\t\t\t\t)\n\t\t\t\treturn R.succeed(\n\t\t\t\t\tsized(budget, {\n\t\t\t\t\t\t_tag: \"Rules\",\n\t\t\t\t\t\tplace: at,\n\t\t\t\t\t\tsize: 1 + found.length,\n\t\t\t\t\t\talgorithm,\n\t\t\t\t\t\trules: found,\n\t\t\t\t\t}),\n\t\t\t\t)\n\t\t\t}\n\t\t\tcase \"All\":\n\t\t\tcase \"Any\":\n\t\t\tcase \"FirstOf\":\n\t\t\t\treturn R.map(\n\t\t\t\t\tR.forEach(\n\t\t\t\t\t\tJson.readList(input, \"policies\", at, budget),\n\t\t\t\t\t\t(value, index) => below(value, \"policies\", index),\n\t\t\t\t\t),\n\t\t\t\t\t(policies) =>\n\t\t\t\t\t\tsized(budget, {\n\t\t\t\t\t\t\t_tag: tag,\n\t\t\t\t\t\t\tplace: at,\n\t\t\t\t\t\t\tsize: Arr.reduce(policies, 1, (total, plan) => total + plan.size),\n\t\t\t\t\t\t\tpolicies,\n\t\t\t\t\t\t}),\n\t\t\t\t)\n\t\t\tcase \"Ceiling\": {\n\t\t\t\tconst cap = Json.read(input, \"cap\", at)\n\t\t\t\tconst policy = Json.read(input, \"policy\", at)\n\t\t\t\treturn R.flatMap(below(cap, \"cap\"), (capPlan) =>\n\t\t\t\t\tR.map(below(policy, \"policy\"), (policyPlan) =>\n\t\t\t\t\t\tsized(budget, {\n\t\t\t\t\t\t\t_tag: \"Ceiling\",\n\t\t\t\t\t\t\tplace: at,\n\t\t\t\t\t\t\tsize: 1 + capPlan.size + policyPlan.size,\n\t\t\t\t\t\t\tcap: capPlan,\n\t\t\t\t\t\t\tpolicy: policyPlan,\n\t\t\t\t\t\t}),\n\t\t\t\t\t),\n\t\t\t\t)\n\t\t\t}\n\t\t\tdefault:\n\t\t\t\tthrow Json.problemAt(Place.at(at, \"_tag\"), \"The node has no known tag\")\n\t\t}\n\t})\n}\n\n/** Stops a plan past `policyNodes`, with the message of the nodes. */\nconst sized = (budget: Json.Budget, plan: Plan): Plan => {\n\tconst most = budget.limits.policyNodes\n\tif (plan.size > most) {\n\t\tthrow Json.problemAt(\n\t\t\tplan.place,\n\t\t\t`A policy tree has ${String(plan.size)} nodes. The most is ${String(most)}.`,\n\t\t)\n\t}\n\treturn plan\n}\n\n/**\n * Makes the nodes of a plan, with the decoded rules. It is a walk of the\n * trampoline, so a deep plan does not fill the stack.\n */\nconst build = (\n\tbudget: Json.Budget,\n\trules: ReadonlyArray<Rule.Rule>,\n): ((plan: Plan) => Node) =>\n\tTrampoline.adaptive<Plan, Node>((R, child) => (plan, depth) => {\n\t\t// A node past a limit is a problem at its place. The constructor checks\n\t\t// the ceilings, and the decoder checks the limits of the domain, with\n\t\t// the nodes and values of the conditions.\n\t\tconst make = (made: () => Node): Node => {\n\t\t\tconst node = (() => {\n\t\t\t\ttry {\n\t\t\t\t\treturn made()\n\t\t\t\t} catch (error) {\n\t\t\t\t\tif (error instanceof Limits.LimitExceeded) {\n\t\t\t\t\t\tthrow Json.problemAt(plan.place, error.message)\n\t\t\t\t\t}\n\t\t\t\t\tthrow error\n\t\t\t\t}\n\t\t\t})()\n\t\t\tconst most = budget.limits.policyNodes\n\t\t\tif (node.size > most) {\n\t\t\t\tthrow Json.problemAt(\n\t\t\t\t\tplan.place,\n\t\t\t\t\t`A policy tree has ${String(node.size)} nodes. The most is ${String(most)}.`,\n\t\t\t\t)\n\t\t\t}\n\t\t\treturn node\n\t\t}\n\t\tswitch (plan._tag) {\n\t\t\tcase \"Rules\":\n\t\t\t\treturn R.succeed(\n\t\t\t\t\tmake(\n\t\t\t\t\t\t() =>\n\t\t\t\t\t\t\tnew PolicyNodes.Rules({\n\t\t\t\t\t\t\t\talgorithm: plan.algorithm,\n\t\t\t\t\t\t\t\trules: Arr.map(plan.rules, (index) => rules[index]!),\n\t\t\t\t\t\t\t}),\n\t\t\t\t\t),\n\t\t\t\t)\n\t\t\tcase \"All\":\n\t\t\tcase \"Any\":\n\t\t\tcase \"FirstOf\": {\n\t\t\t\tconst tag = plan._tag\n\t\t\t\treturn R.map(\n\t\t\t\t\tR.forEach(plan.policies, (inner) => child(inner, depth + 1)),\n\t\t\t\t\t(policies) => make(() => new PolicyNodes[tag](policies)),\n\t\t\t\t)\n\t\t\t}\n\t\t\tcase \"Ceiling\":\n\t\t\t\treturn R.flatMap(child(plan.cap, depth + 1), (cap) =>\n\t\t\t\t\tR.map(child(plan.policy, depth + 1), (policy) =>\n\t\t\t\t\t\tmake(() => new PolicyNodes.Ceiling({ cap, policy })),\n\t\t\t\t\t),\n\t\t\t\t)\n\t\t}\n\t})\n\n/** Runs a step that may throw a problem, and puts the problem in the error. */\nconst attempt = <A>(step: () => A): Effect.Effect<A, Json.Problem> =>\n\tEffect.try({\n\t\ttry: step,\n\t\tcatch: (error) => {\n\t\t\tif (error instanceof Json.Problem) {\n\t\t\t\treturn error\n\t\t\t}\n\t\t\tthrow error\n\t\t},\n\t})\n\n/**\n * Decodes the JSON form of a policy tree for a domain. It reads the tree\n * first, then the `meta` of each rule as an Effect, then the rules, and it\n * makes the nodes last.\n */\nexport const decode = (\n\tinput: unknown,\n\tdomain: Domain.Any,\n): Effect.Effect<Node, Json.Problem> =>\n\tEffect.gen(function* () {\n\t\tconst budget: Json.Budget = { used: 0, limits: domain.limits }\n\t\t// The rules of this decode. The list belongs to this decode.\n\t\tconst inputs: Array<RuleInput> = []\n\t\tconst plan = yield* attempt(() =>\n\t\t\treadTree(budget, inputs)({ input, at: undefined }),\n\t\t)\n\t\tconst metas = yield* Effect.forEach(inputs, (rule) =>\n\t\t\tdecodeMeta(rule, domain, budget),\n\t\t)\n\t\tconst rules = yield* attempt(() =>\n\t\t\tArr.map(inputs, (rule, index) =>\n\t\t\t\tdecodeRule(rule, metas[index], budget, domain),\n\t\t\t),\n\t\t)\n\t\treturn yield* attempt(() => build(budget, rules)(plan))\n\t})\n"],"mappings":"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"}
@@ -8,12 +8,12 @@ import { Ceiling, Rules, policy_nodes_exports, rulesOf } from "./policy-nodes.js
8
8
  import { decode, encode, syncMeta, toJson } from "./policy-json.js";
9
9
  import * as Arr from "effect/Array";
10
10
  import * as Effect from "effect/Effect";
11
+ import * as Option from "effect/Option";
11
12
  import * as Schema from "effect/Schema";
12
13
  import * as Data from "effect/Data";
13
14
  import { dual } from "effect/Function";
14
15
  import * as SchemaIssue from "effect/SchemaIssue";
15
16
  import * as SchemaTransformation from "effect/SchemaTransformation";
16
- import * as Option from "effect/Option";
17
17
  // src/internal/policy.ts
18
18
  /**
19
19
  * The class of every policy value. Two policies of one domain with the same
@@ -4,9 +4,9 @@ import { always, never } from "./nodes.js";
4
4
  import { and, keyOf, not, or, simplify, testCompare } from "./algebra.js";
5
5
  import { anyKinds, lookup } from "./catalog.js";
6
6
  import * as Arr from "effect/Array";
7
+ import * as Option from "effect/Option";
7
8
  import * as Data from "effect/Data";
8
9
  import { isObject } from "effect/Predicate";
9
- import * as Option from "effect/Option";
10
10
  // src/internal/search.ts
11
11
  /**
12
12
  * The search for a value that makes a condition true. Each test of a
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nmnmcc/ability",
3
- "version": "2.1.3",
3
+ "version": "2.1.5",
4
4
  "description": "Typed, composable authorization policies for Effect programs.",
5
5
  "license": "Apache-2.0",
6
6
  "type": "module",
@@ -17,6 +17,7 @@ import * as Hash from "effect/Hash"
17
17
  import * as HashMap from "effect/HashMap"
18
18
  import * as HashSet from "effect/HashSet"
19
19
  import * as Layer from "effect/Layer"
20
+ import * as Option from "effect/Option"
20
21
  import * as Result from "effect/Result"
21
22
  import * as Schema from "effect/Schema"
22
23
  import type * as Authorizer from "../Authorizer.ts"
@@ -148,6 +149,12 @@ class PrincipalKey implements Equal.Equal {
148
149
  * part does not make the text very long. The numbers are in a native `Map`,
149
150
  * because the walk must find an object by the object itself. The map belongs
150
151
  * to one walk.
152
+ *
153
+ * A value that keeps its data inside, with no own fields, as a `Date`, a
154
+ * `URL` or a `RegExp` with the place of its next match, is in the text with
155
+ * its data, and so is an `Option`.
156
+ * For another such value, the walk can not see the data, so it throws, and
157
+ * the principal gets no key: it is not kept.
151
158
  */
152
159
  const orderOf = (root: unknown): string => {
153
160
  const ids = new Map<object, number>()
@@ -168,6 +175,24 @@ const orderOf = (root: unknown): string => {
168
175
  ids.set(value, ids.size)
169
176
  const items = (list: ReadonlyArray<unknown>) =>
170
177
  R.forEach(list, (item) => child(item, depth + 1))
178
+ if (value instanceof Date) {
179
+ return R.succeed(`date:${String(value.getTime())}`)
180
+ }
181
+ if (value instanceof URL) {
182
+ return R.succeed(`url:${JSON.stringify(value.href)}`)
183
+ }
184
+ if (value instanceof RegExp) {
185
+ // A `RegExp` with the flag `g` or `y` keeps the place of its next
186
+ // match in `lastIndex`, which `Equal` does not see.
187
+ return R.succeed(
188
+ `regexp:${JSON.stringify(String(value))}:${String(value.lastIndex)}`,
189
+ )
190
+ }
191
+ if (Option.isOption(value)) {
192
+ return Option.isSome(value)
193
+ ? R.map(child(value.value, depth + 1), (text) => `some(${text})`)
194
+ : R.succeed("none")
195
+ }
171
196
  if (Array.isArray(value)) {
172
197
  return R.map(items(value), (texts) => `[${texts.join(",")}]`)
173
198
  }
@@ -184,6 +209,14 @@ const orderOf = (root: unknown): string => {
184
209
  )
185
210
  }
186
211
  const names = Object.keys(value)
212
+ const prototype: unknown = Object.getPrototypeOf(value)
213
+ if (
214
+ names.length === 0 &&
215
+ prototype !== Object.prototype &&
216
+ prototype !== null
217
+ ) {
218
+ throw new Error("The data of a part of the principal is not in fields")
219
+ }
187
220
  return R.map(
188
221
  items(Arr.map(names, (name) => (value as Record<string, unknown>)[name])),
189
222
  (texts) =>
@@ -153,11 +153,20 @@ const orderedMeet = (left: Catalog.Kinds, right: Catalog.Kinds): boolean =>
153
153
  (hasStrings(left) && hasStrings(right)) ||
154
154
  (hasNumbers(left) && hasNumbers(right))
155
155
 
156
- /** The texts of the fields of a catalog that have only names, for suggestions. */
157
- const namesOf = (catalog: Catalog.Catalog): ReadonlyArray<string> =>
156
+ /**
157
+ * The texts of the fields of a catalog that have only names, for
158
+ * suggestions, with the limits of the domain. The domain checked the size of
159
+ * the list when it was made.
160
+ */
161
+ const namesOf = (
162
+ catalog: Catalog.Catalog,
163
+ limits: Limits.Limits,
164
+ ): ReadonlyArray<string> =>
158
165
  Arr.map(
159
- Arr.filter(catalog.fields, (field) =>
160
- Arr.every(field.path, (segment) => typeof segment === "string"),
166
+ Arr.filter(
167
+ Catalog.fieldsOf(catalog, limits.fieldPathParts, limits.catalogFields),
168
+ (field) =>
169
+ Arr.every(field.path, (segment) => typeof segment === "string"),
161
170
  ),
162
171
  (field) => field.text,
163
172
  )
@@ -192,7 +201,7 @@ const fieldIssue = (
192
201
  {
193
202
  path: at,
194
203
  issue: suggestIn(context.memo, message, field, without[0]!.kind, () =>
195
- namesOf(without[0]!.catalog),
204
+ namesOf(without[0]!.catalog, context.memo.limits),
196
205
  ),
197
206
  },
198
207
  ]
@@ -332,7 +341,7 @@ const principalField = (
332
341
  `The principal has no field "${ref}"`,
333
342
  ref,
334
343
  "~principal",
335
- () => namesOf(context.domain.principalCatalog),
344
+ () => namesOf(context.domain.principalCatalog, context.memo.limits),
336
345
  ),
337
346
  }
338
347
  }
@@ -629,7 +638,15 @@ const conditionIssues = (
629
638
  case "Exists":
630
639
  return room(0) ? here(readIssues(context, node.field, ["field"])) : []
631
640
  case "Under":
632
- return here(underIssues(context, node.prefix, []))
641
+ // The check reads the path of each resource of the rule for each
642
+ // part of the prefix, so it counts that work first.
643
+ return spendChecks(
644
+ context.memo,
645
+ 1 + node.prefix.length,
646
+ context.paths.length,
647
+ )
648
+ ? here(underIssues(context, node.prefix, []))
649
+ : []
633
650
  default:
634
651
  return []
635
652
  }
@@ -175,12 +175,6 @@ export interface Branch {
175
175
 
176
176
  /** The catalog of the fields of a Schema. */
177
177
  export interface Catalog {
178
- /**
179
- * The fields that a list shows, a field before the fields below it. For a
180
- * recursive Schema, the list goes into each recursive place one time on
181
- * each path, so it is finite.
182
- */
183
- readonly fields: ReadonlyArray<Field>
184
178
  /** The places, by id. The root is the place with the id 0. */
185
179
  readonly branches: ReadonlyArray<Branch>
186
180
  /**
@@ -304,6 +298,13 @@ const isStruct = (
304
298
  ast !== undefined &&
305
299
  (ast.propertySignatures.length > 0 || ast.indexSignatures.length > 0)
306
300
 
301
+ /** Tests if a Schema holds only values that are not objects, as `bigint`. */
302
+ const isPrimitive = (ast: SchemaAST.AST): boolean =>
303
+ ast._tag === "BigInt" ||
304
+ ast._tag === "Symbol" ||
305
+ ast._tag === "UniqueSymbol" ||
306
+ (ast._tag === "Literal" && typeof ast.literal === "bigint")
307
+
307
308
  /** The kinds of the values of a Schema that is not a struct. */
308
309
  const kindsOf = (ast: SchemaAST.AST): Kinds => {
309
310
  switch (ast._tag) {
@@ -481,6 +482,15 @@ const placeOf = (place: Place): Made => {
481
482
  const valueKinds = Arr.reduce(values, noKinds, (kinds, schema) =>
482
483
  joinKinds(kinds, kindsOf(structOf(schema) ?? schema)),
483
484
  )
485
+ // A value of a branch may be an object, with parts that the Schema does
486
+ // not name. A `bigint` or a `symbol` is not a scalar that a test takes,
487
+ // so its kinds say `object`, but it has no parts: a read below it gives
488
+ // `null`, as below a string.
489
+ const objects = Arr.some(
490
+ values,
491
+ (schema) =>
492
+ !isPrimitive(schema) && kindsOf(structOf(schema) ?? schema).object,
493
+ )
484
494
  const kinds = place.top
485
495
  ? anyKinds
486
496
  : joinKinds(
@@ -502,8 +512,7 @@ const placeOf = (place: Place): Made => {
502
512
  // decision may get a value made by hand. A value that may be any
503
513
  // object, as one of `Schema.Unknown` may, may have the name with any
504
514
  // value too.
505
- const top =
506
- place.top || valueKinds.object || properties.length < structs.length
515
+ const top = place.top || objects || properties.length < structs.length
507
516
  const below: Place = {
508
517
  schemas: Arr.flatMap(properties, (property) =>
509
518
  alternatives(property.type, new Set()),
@@ -522,8 +531,7 @@ const placeOf = (place: Place): Made => {
522
531
  return [name, below] as const
523
532
  })
524
533
  const signatures = Arr.flatMap(records, (struct) => struct.indexSignatures)
525
- const keyTop =
526
- place.top || valueKinds.object || records.length < structs.length
534
+ const keyTop = place.top || objects || records.length < structs.length
527
535
  return {
528
536
  branch: {
529
537
  kinds,
@@ -531,11 +539,7 @@ const placeOf = (place: Place): Made => {
531
539
  structs: structs.length > 0,
532
540
  // A field that is a scalar in one branch of a union and a struct in
533
541
  // another holds parts too.
534
- open:
535
- records.length > 0 ||
536
- structs.length > 0 ||
537
- valueKinds.object ||
538
- place.top,
542
+ open: records.length > 0 || structs.length > 0 || objects || place.top,
539
543
  plain: place.plain,
540
544
  key:
541
545
  signatures.length === 0
@@ -651,13 +655,18 @@ const fieldAt = (
651
655
  * The fields that a list shows, a field before the fields below it, down to
652
656
  * `parts` parts. A path goes into a place that is on it already one time
653
657
  * only, so the list of a recursive Schema is finite. The walk keeps a stack
654
- * of the places to visit, so a deep Schema does not fill the stack. The
655
- * stack belongs to this walk.
658
+ * of the places to visit, so a deep Schema does not fill the stack. A Schema
659
+ * that shares one part in many places may have very many fields, so the
660
+ * walk counts the fields that it makes, and it stops at the first one past
661
+ * `most`. The stack and the count belong to this walk.
656
662
  */
657
663
  const listOf = (
658
664
  branches: ReadonlyArray<Branch>,
659
665
  parts: number,
666
+ most: number,
667
+ thing: string,
660
668
  ): ReadonlyArray<Field> => {
669
+ const count = { fields: 0 }
661
670
  interface Visit {
662
671
  readonly branch: Branch
663
672
  readonly path: ReadonlyArray<Segment>
@@ -708,13 +717,14 @@ const listOf = (
708
717
  visit.path.length === 0
709
718
  ? []
710
719
  : [fieldAt(visit.branch, visit.path, parts)]
711
- if (field.length > 0 && rest.length + 1 > Limits.ceilings.catalogFields) {
720
+ count.fields += field.length
721
+ if (count.fields > most) {
712
722
  throw Limits.exceeded(
713
- "A Schema",
723
+ thing,
714
724
  "catalogFields",
715
725
  "fields",
716
- rest.length + 1,
717
- Limits.ceilings.catalogFields,
726
+ count.fields,
727
+ most,
718
728
  )
719
729
  }
720
730
  return Option.some([field, rest] as const)
@@ -723,22 +733,25 @@ const listOf = (
723
733
  }
724
734
 
725
735
  /**
726
- * The fields that a list shows for paths of at most `parts` parts, as for a
727
- * domain with that limit. See `Catalog.fields`.
736
+ * The fields that a list shows, a field before the fields below it, for
737
+ * paths of at most `parts` parts. For a recursive Schema, the list goes into
738
+ * each recursive place one time on each path, so it is finite. A list with
739
+ * more than `most` fields is an error, as for a domain with these limits.
740
+ * Each call makes the list again: a domain checks its size one time, and a
741
+ * list of permitted fields reads it.
728
742
  */
729
743
  export const fieldsOf = (
730
744
  catalog: Catalog,
731
- parts: number,
732
- ): ReadonlyArray<Field> =>
733
- parts === Limits.fieldPathParts
734
- ? catalog.fields
735
- : listOf(catalog.branches, parts)
745
+ parts: number = Limits.fieldPathParts,
746
+ most: number = Limits.ceilings.catalogFields,
747
+ thing: string = "A Schema",
748
+ ): ReadonlyArray<Field> => listOf(catalog.branches, parts, most, thing)
736
749
 
737
750
  /**
738
- * Makes the catalog of a Schema, with the list of its fields for the default
739
- * limit of the parts of a path. It throws when a field name has a dot or is
740
- * empty, and when the Schema has more places or fields than the ceiling of
741
- * `catalogFields`. A domain checks the size of the list with its own limit.
751
+ * Makes the catalog of a Schema. It throws when a field name has a dot or is
752
+ * empty, and when the Schema has more places than the ceiling of
753
+ * `catalogFields`. A domain checks the size of the list of the fields with
754
+ * its own limit: see {@link fieldsOf}.
742
755
  */
743
756
  export const catalogOf = (ast: SchemaAST.AST): Catalog => {
744
757
  const branches = branchesOf({
@@ -748,7 +761,6 @@ export const catalogOf = (ast: SchemaAST.AST): Catalog => {
748
761
  plain: true,
749
762
  })
750
763
  return {
751
- fields: listOf(branches, Limits.fieldPathParts),
752
764
  branches,
753
765
  edges: Arr.reduce(
754
766
  branches,
@@ -390,7 +390,11 @@ export const permittedFields = (
390
390
  const listed = Arr.filter(
391
391
  // The list for the limit of the parts of a path of the domain, as the
392
392
  // decision of each field reads it.
393
- Catalog.fieldsOf(descriptor.catalog, policy.domain.limits.fieldPathParts),
393
+ Catalog.fieldsOf(
394
+ descriptor.catalog,
395
+ policy.domain.limits.fieldPathParts,
396
+ policy.domain.limits.catalogFields,
397
+ ),
394
398
  (field) =>
395
399
  field.listed &&
396
400
  Arr.every(field.path, (segment) => typeof segment === "string"),
@@ -21,22 +21,17 @@ class DomainData extends Data.Class<{
21
21
  readonly limits: Limits.Limits
22
22
  }> {}
23
23
 
24
- /** Stops a catalog with more fields in its list than the domain takes. */
24
+ /**
25
+ * Stops a catalog with more fields in its list than the domain takes. The
26
+ * list stops at the first field past the limit, so a Schema with very many
27
+ * fields does not take very much memory first.
28
+ */
25
29
  const checkCatalog = (
26
30
  catalog: Catalog.Catalog,
27
31
  limits: Limits.Limits,
28
32
  name: string,
29
33
  ): void => {
30
- const fields = Catalog.fieldsOf(catalog, limits.fieldPathParts).length
31
- if (fields > limits.catalogFields) {
32
- throw Limits.exceeded(
33
- name,
34
- "catalogFields",
35
- "fields",
36
- fields,
37
- limits.catalogFields,
38
- )
39
- }
34
+ Catalog.fieldsOf(catalog, limits.fieldPathParts, limits.catalogFields, name)
40
35
  }
41
36
 
42
37
  /**