@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":"patterns.js","names":["Limits.patternParts","Catalog.keyFits","Limits.fieldPathParts"],"sources":["../../src/internal/patterns.ts"],"sourcesContent":["/**\n * Field patterns, as in `settings.*`, `secret*` or `**.secret`. A pattern is\n * a list of parts with dots between them:\n * - A name with no star matches that name.\n * - A part with stars matches a name, and each star matches any text with no\n * dot, as `secret*` matches `secretKey`.\n * - `**` matches any number of names, also none. It must be a whole part.\n *\n * A pattern that matches a field matches each field below it too. So `pay`\n * matches `pay.amount`, as `pay.**` does.\n *\n * The match goes one name at a time, and it keeps the set of places in the\n * pattern that it may be at. It does not go back, so the time is at most the\n * number of names times the number of parts.\n */\nimport * as Arr from \"effect/Array\"\nimport * as Option from \"effect/Option\"\nimport * as Result from \"effect/Result\"\nimport * as Catalog from \"./catalog.ts\"\nimport * as Limits from \"./limits.ts\"\n\n/** One part of a pattern. */\nexport type Part =\n\t| { readonly _tag: \"Name\"; readonly name: string }\n\t/** The texts between the stars of the part. */\n\t| { readonly _tag: \"Glob\"; readonly pieces: ReadonlyArray<string> }\n\t| { readonly _tag: \"Globstar\" }\n\n/** A pattern, as a list of parts. Two `**` parts are never next to each other. */\nexport type Pattern = ReadonlyArray<Part>\n\nconst globstar: Part = { _tag: \"Globstar\" }\n\nconst partOf = (text: string): Part =>\n\ttext === \"**\"\n\t\t? globstar\n\t\t: text.includes(\"*\")\n\t\t\t? { _tag: \"Glob\", pieces: text.split(\"*\") }\n\t\t\t: { _tag: \"Name\", name: text }\n\n/**\n * Reads a pattern. It fails with the problem when the pattern has `**`\n * inside a part, or has more parts than `parts`, the `patternParts` of a\n * domain.\n */\nexport const parse = (\n\ttext: string,\n\tparts: number = Limits.patternParts,\n): Result.Result<Pattern, string> => {\n\tconst texts = text.split(\".\", parts + 1)\n\tif (texts.length > parts) {\n\t\treturn Result.fail(\n\t\t\t`The field pattern \"${text}\" has more than ${String(parts)} parts. The most is ${String(parts)}.`,\n\t\t)\n\t}\n\tif (Arr.some(texts, (part) => part !== \"**\" && part.includes(\"**\"))) {\n\t\treturn Result.fail(\n\t\t\t`The field pattern \"${text}\" has \"**\" inside a part. \"**\" must be a whole part`,\n\t\t)\n\t}\n\t// `a.**.**` matches what `a.**` matches.\n\treturn Result.succeed(\n\t\tArr.dedupeAdjacentWith(\n\t\t\tArr.map(texts, partOf),\n\t\t\t(left, right) => left._tag === \"Globstar\" && right._tag === \"Globstar\",\n\t\t),\n\t)\n}\n\n/**\n * Reads a pattern of a bound rule. The binding of the rule took the pattern\n * with the limits of its domain, so this reads it up to the ceiling, and a\n * problem here is a defect.\n */\nexport const parseBound = (text: string): Pattern =>\n\tResult.getOrThrowWith(\n\t\tparse(text, Limits.ceilings.patternParts),\n\t\t(problem) => new Error(problem),\n\t)\n\n/** Tests if a name matches the pieces of a part with stars. */\nconst globMatches = (pieces: ReadonlyArray<string>, name: string): boolean => {\n\tconst first = pieces[0]!\n\tconst last = pieces[pieces.length - 1]!\n\tconst middle = pieces.slice(1, -1)\n\tif (\n\t\tname.length < first.length + last.length ||\n\t\t!name.startsWith(first) ||\n\t\t!name.endsWith(last)\n\t) {\n\t\treturn false\n\t}\n\t// Each piece in the middle is found as early as it can be. An earlier\n\t// place never leaves less room for the pieces after it.\n\tconst end = name.length - last.length\n\tconst place = Arr.reduce(middle, first.length as number, (at, piece) => {\n\t\tif (at < 0) {\n\t\t\treturn at\n\t\t}\n\t\tconst found = name.indexOf(piece, at)\n\t\treturn found < 0 || found + piece.length > end ? -1 : found + piece.length\n\t})\n\treturn place >= 0\n}\n\n/**\n * Tests if a part matches one segment of a path. A part with stars matches\n * any key of a record. A name matches a key of a record when the Schema of\n * the keys takes it.\n */\nconst partFits = (part: Part, segment: Catalog.Segment): boolean => {\n\tswitch (part._tag) {\n\t\tcase \"Globstar\":\n\t\t\treturn true\n\t\tcase \"Glob\":\n\t\t\treturn typeof segment !== \"string\" || globMatches(part.pieces, segment)\n\t\tcase \"Name\":\n\t\t\treturn typeof segment === \"string\"\n\t\t\t\t? part.name === segment\n\t\t\t\t: Catalog.keyFits(segment, part.name)\n\t}\n}\n\n/** The places of a match: the parts that it may take next. */\ntype States = ReadonlyArray<number>\n\n/** Adds the place after each `**`, since a `**` may match no names. */\nconst close = (pattern: Pattern, states: States): States =>\n\t// A native `Set` keeps the first copy of each place, in order. It is\n\t// faster than `Arr.dedupe`, and this is on the path of each match.\n\tArray.from(\n\t\tnew Set(\n\t\t\tArr.flatMap(states, (state) =>\n\t\t\t\tpattern[state]?._tag === \"Globstar\" ? [state, state + 1] : [state],\n\t\t\t),\n\t\t),\n\t)\n\nconst start = (pattern: Pattern): States => close(pattern, [0])\n\n/** Gives the places after one more segment. A `**` that takes it stays. */\nconst step = (\n\tpattern: Pattern,\n\tstates: States,\n\tsegment: Catalog.Segment,\n): States =>\n\tclose(\n\t\tpattern,\n\t\tArr.flatMap(states, (state) => {\n\t\t\tconst part = pattern[state]\n\t\t\treturn part === undefined\n\t\t\t\t? []\n\t\t\t\t: part._tag === \"Globstar\"\n\t\t\t\t\t? [state]\n\t\t\t\t\t: partFits(part, segment)\n\t\t\t\t\t\t? [state + 1]\n\t\t\t\t\t\t: []\n\t\t}),\n\t)\n\nconst isDone = (pattern: Pattern, states: States): boolean =>\n\tstates.includes(pattern.length)\n\n/**\n * Tests if a pattern matches a field, or a field above it. The field is a\n * list of names.\n */\nexport const matches = (\n\tpattern: Pattern,\n\tnames: ReadonlyArray<string>,\n): boolean => {\n\tconst found = Arr.reduce(\n\t\tnames,\n\t\t{ states: start(pattern), done: isDone(pattern, start(pattern)) },\n\t\t(before, name) => {\n\t\t\tif (before.done || before.states.length === 0) {\n\t\t\t\treturn before\n\t\t\t}\n\t\t\tconst states = step(pattern, before.states, name)\n\t\t\treturn { states, done: isDone(pattern, states) }\n\t\t},\n\t)\n\treturn found.done\n}\n\n/**\n * Tests if a pattern may match a field below a field, with more names after\n * the names of the field.\n */\nexport const matchesBelow = (\n\tpattern: Pattern,\n\tnames: ReadonlyArray<string>,\n): boolean =>\n\tArr.some(\n\t\tArr.reduce(names, start(pattern), (states, name) =>\n\t\t\tstep(pattern, states, name),\n\t\t),\n\t\t(state) => state < pattern.length,\n\t)\n\n/**\n * Tests if a pattern matches some field of a catalog, with at most `parts`\n * parts. A recursive Schema has no end of fields, so the search goes over the\n * pairs of a place of the catalog and the places of the match: each pair\n * comes one time, a level after a level, so the search is short, and it\n * keeps a queue, so a deep Schema does not fill the stack. The set of the\n * pairs is native, with keys that are numbers, and the set and the queue\n * belong to this call.\n */\nexport const fitsCatalog = (\n\tpattern: Pattern,\n\tcatalog: Catalog.Catalog,\n\tparts: number = Limits.fieldPathParts,\n): boolean => {\n\tinterface Visit {\n\t\treadonly branch: Catalog.Branch\n\t\treadonly states: States\n\t\treadonly depth: number\n\t}\n\tconst seen = new Set<string>()\n\tconst next = (visit: Visit): ReadonlyArray<Visit> => {\n\t\tif (visit.states.length === 0 || visit.depth === parts) {\n\t\t\treturn []\n\t\t}\n\t\tconst steps: ReadonlyArray<readonly [Catalog.Segment, number]> = [\n\t\t\t...visit.branch.children,\n\t\t\t...(visit.branch.key === undefined || visit.branch.keyBranch === undefined\n\t\t\t\t? []\n\t\t\t\t: [[visit.branch.key, visit.branch.keyBranch] as const]),\n\t\t]\n\t\treturn Arr.flatMap(steps, ([segment, id]) => {\n\t\t\tconst states = step(pattern, visit.states, segment)\n\t\t\tconst key = `${String(id)}|${states.join(\",\")}`\n\t\t\tif (states.length === 0 || seen.has(key)) {\n\t\t\t\treturn []\n\t\t\t}\n\t\t\tseen.add(key)\n\t\t\treturn [{ branch: catalog.branches[id]!, states, depth: visit.depth + 1 }]\n\t\t})\n\t}\n\tconst queue: Array<Visit> = [\n\t\t{ branch: catalog.root, states: start(pattern), depth: 0 },\n\t]\n\t// The walk stops at the first field that the pattern matches.\n\tconst found = (visit: Visit) =>\n\t\tvisit.depth > 0 && isDone(pattern, visit.states)\n\treturn Arr.some(\n\t\tArr.unfold(0, (index) => {\n\t\t\tconst visit = queue[index]\n\t\t\tif (visit === undefined) {\n\t\t\t\treturn Option.none()\n\t\t\t}\n\t\t\tif (found(visit)) {\n\t\t\t\treturn Option.some([true, queue.length] as const)\n\t\t\t}\n\t\t\tqueue.push(...next(visit))\n\t\t\treturn Option.some([false, index + 1] as const)\n\t\t}),\n\t\t(match) => match,\n\t)\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"patterns.js","names":["Limits.patternParts","Catalog.keyFits","Limits.fieldPathParts"],"sources":["../../src/internal/patterns.ts"],"sourcesContent":["/**\n * Field patterns, as in `settings.*`, `secret*` or `**.secret`. A pattern is\n * a list of parts with dots between them:\n * - A name with no star matches that name.\n * - A part with stars matches a name, and each star matches any text with no\n * dot, as `secret*` matches `secretKey`.\n * - `**` matches any number of names, also none. It must be a whole part.\n *\n * A pattern that matches a field matches each field below it too. So `pay`\n * matches `pay.amount`, as `pay.**` does.\n *\n * The match goes one name at a time, and it keeps the set of places in the\n * pattern that it may be at. It does not go back, so the time is at most the\n * number of names times the number of parts.\n */\nimport * as Arr from \"effect/Array\"\nimport * as Option from \"effect/Option\"\nimport * as Result from \"effect/Result\"\nimport * as Catalog from \"./catalog.ts\"\nimport * as Limits from \"./limits.ts\"\n\n/** One part of a pattern. */\nexport type Part =\n\t| { readonly _tag: \"Name\"; readonly name: string }\n\t/** The texts between the stars of the part. */\n\t| { readonly _tag: \"Glob\"; readonly pieces: ReadonlyArray<string> }\n\t| { readonly _tag: \"Globstar\" }\n\n/** A pattern, as a list of parts. Two `**` parts are never next to each other. */\nexport type Pattern = ReadonlyArray<Part>\n\nconst globstar: Part = { _tag: \"Globstar\" }\n\nconst partOf = (text: string): Part =>\n\ttext === \"**\"\n\t\t? globstar\n\t\t: text.includes(\"*\")\n\t\t\t? { _tag: \"Glob\", pieces: text.split(\"*\") }\n\t\t\t: { _tag: \"Name\", name: text }\n\n/**\n * Reads a pattern. It fails with the problem when the pattern has `**`\n * inside a part, or has more parts than `parts`, the `patternParts` of a\n * domain.\n */\nexport const parse = (\n\ttext: string,\n\tparts: number = Limits.patternParts,\n): Result.Result<Pattern, string> => {\n\tconst texts = text.split(\".\", parts + 1)\n\tif (texts.length > parts) {\n\t\treturn Result.fail(\n\t\t\t`The field pattern \"${text}\" has more than ${String(parts)} parts. The most is ${String(parts)}.`,\n\t\t)\n\t}\n\tif (Arr.some(texts, (part) => part !== \"**\" && part.includes(\"**\"))) {\n\t\treturn Result.fail(\n\t\t\t`The field pattern \"${text}\" has \"**\" inside a part. \"**\" must be a whole part`,\n\t\t)\n\t}\n\t// `a.**.**` matches what `a.**` matches.\n\treturn Result.succeed(\n\t\tArr.dedupeAdjacentWith(\n\t\t\tArr.map(texts, partOf),\n\t\t\t(left, right) => left._tag === \"Globstar\" && right._tag === \"Globstar\",\n\t\t),\n\t)\n}\n\n/**\n * Reads a pattern of a bound rule. The binding of the rule took the pattern\n * with the limits of its domain, so this reads it up to the ceiling, and a\n * problem here is a defect.\n */\nexport const parseBound = (text: string): Pattern =>\n\tResult.getOrThrowWith(\n\t\tparse(text, Limits.ceilings.patternParts),\n\t\t(problem) => new Error(problem),\n\t)\n\n/** Tests if a name matches the pieces of a part with stars. */\nconst globMatches = (pieces: ReadonlyArray<string>, name: string): boolean => {\n\tconst first = pieces[0]!\n\tconst last = pieces[pieces.length - 1]!\n\tconst middle = pieces.slice(1, -1)\n\tif (\n\t\tname.length < first.length + last.length ||\n\t\t!name.startsWith(first) ||\n\t\t!name.endsWith(last)\n\t) {\n\t\treturn false\n\t}\n\t// Each piece in the middle is found as early as it can be. An earlier\n\t// place never leaves less room for the pieces after it.\n\tconst end = name.length - last.length\n\tconst place = Arr.reduce(middle, first.length as number, (at, piece) => {\n\t\tif (at < 0) {\n\t\t\treturn at\n\t\t}\n\t\tconst found = name.indexOf(piece, at)\n\t\treturn found < 0 || found + piece.length > end ? -1 : found + piece.length\n\t})\n\treturn place >= 0\n}\n\n/**\n * Tests if a part matches one segment of a path. A part with stars matches\n * any key of a record. A name matches a key of a record when the Schema of\n * the keys takes it.\n */\nconst partFits = (part: Part, segment: Catalog.Segment): boolean => {\n\tswitch (part._tag) {\n\t\tcase \"Globstar\":\n\t\t\treturn true\n\t\tcase \"Glob\":\n\t\t\treturn typeof segment !== \"string\" || globMatches(part.pieces, segment)\n\t\tcase \"Name\":\n\t\t\treturn typeof segment === \"string\"\n\t\t\t\t? part.name === segment\n\t\t\t\t: Catalog.keyFits(segment, part.name)\n\t}\n}\n\n/** The places of a match: the parts that it may take next. */\ntype States = ReadonlyArray<number>\n\n/** Adds the place after each `**`, since a `**` may match no names. */\nconst close = (pattern: Pattern, states: States): States =>\n\t// A native `Set` keeps the first copy of each place, in order. It is\n\t// faster than `Arr.dedupe`, and this is on the path of each match.\n\tArray.from(\n\t\tnew Set(\n\t\t\tArr.flatMap(states, (state) =>\n\t\t\t\tpattern[state]?._tag === \"Globstar\" ? [state, state + 1] : [state],\n\t\t\t),\n\t\t),\n\t)\n\nconst start = (pattern: Pattern): States => close(pattern, [0])\n\n/** Gives the places after one more segment. A `**` that takes it stays. */\nconst step = (\n\tpattern: Pattern,\n\tstates: States,\n\tsegment: Catalog.Segment,\n): States =>\n\tclose(\n\t\tpattern,\n\t\tArr.flatMap(states, (state) => {\n\t\t\tconst part = pattern[state]\n\t\t\treturn part === undefined\n\t\t\t\t? []\n\t\t\t\t: part._tag === \"Globstar\"\n\t\t\t\t\t? [state]\n\t\t\t\t\t: partFits(part, segment)\n\t\t\t\t\t\t? [state + 1]\n\t\t\t\t\t\t: []\n\t\t}),\n\t)\n\nconst isDone = (pattern: Pattern, states: States): boolean =>\n\tstates.includes(pattern.length)\n\n/**\n * Tests if a pattern matches a field, or a field above it. The field is a\n * list of names.\n */\nexport const matches = (\n\tpattern: Pattern,\n\tnames: ReadonlyArray<string>,\n): boolean => {\n\tconst found = Arr.reduce(\n\t\tnames,\n\t\t{ states: start(pattern), done: isDone(pattern, start(pattern)) },\n\t\t(before, name) => {\n\t\t\tif (before.done || before.states.length === 0) {\n\t\t\t\treturn before\n\t\t\t}\n\t\t\tconst states = step(pattern, before.states, name)\n\t\t\treturn { states, done: isDone(pattern, states) }\n\t\t},\n\t)\n\treturn found.done\n}\n\n/**\n * Tests if a pattern may match a field below a field, with more names after\n * the names of the field.\n */\nexport const matchesBelow = (\n\tpattern: Pattern,\n\tnames: ReadonlyArray<string>,\n): boolean =>\n\tArr.some(\n\t\tArr.reduce(names, start(pattern), (states, name) =>\n\t\t\tstep(pattern, states, name),\n\t\t),\n\t\t(state) => state < pattern.length,\n\t)\n\n/**\n * Tests if a pattern matches some field of a catalog, with at most `parts`\n * parts. A recursive Schema has no end of fields, so the search goes over the\n * pairs of a place of the catalog and one place of the match. There are at\n * most the places of the catalog times the parts of the pattern of them, so\n * the work is the steps of the catalog times the parts of the pattern, as the\n * binding counts it. Each pair comes one time, a level after a level, so the\n * search finds the shortest field first, and it keeps a queue, so a deep\n * Schema does not fill the stack. The set of the pairs is native, and the set\n * and the queue belong to this call.\n */\nexport const fitsCatalog = (\n\tpattern: Pattern,\n\tcatalog: Catalog.Catalog,\n\tparts: number = Limits.fieldPathParts,\n): boolean => {\n\tinterface Visit {\n\t\treadonly branch: Catalog.Branch\n\t\treadonly state: number\n\t\treadonly depth: number\n\t}\n\tconst seen = new Set<string>()\n\tconst visits = (\n\t\tid: number,\n\t\tstates: States,\n\t\tdepth: number,\n\t): ReadonlyArray<Visit> =>\n\t\tArr.flatMap(states, (state) => {\n\t\t\tconst key = `${String(id)}|${String(state)}`\n\t\t\tif (seen.has(key)) {\n\t\t\t\treturn []\n\t\t\t}\n\t\t\tseen.add(key)\n\t\t\treturn [{ branch: catalog.branches[id]!, state, depth }]\n\t\t})\n\tconst next = (visit: Visit): ReadonlyArray<Visit> => {\n\t\tif (visit.depth === parts) {\n\t\t\treturn []\n\t\t}\n\t\tconst branch = visit.branch\n\t\tconst steps: ReadonlyArray<readonly [Catalog.Segment, number]> =\n\t\t\tbranch.key === undefined || branch.keyBranch === undefined\n\t\t\t\t? branch.children\n\t\t\t\t: [...branch.children, [branch.key, branch.keyBranch] as const]\n\t\treturn Arr.flatMap(steps, ([segment, id]) =>\n\t\t\tvisits(id, step(pattern, [visit.state], segment), visit.depth + 1),\n\t\t)\n\t}\n\tconst queue: Array<Visit> = [...visits(catalog.root.id, start(pattern), 0)]\n\t// The walk stops at the first field that the pattern matches. The visits\n\t// go into the queue one at a time: a spread into a call would pass one\n\t// argument for each, past the most arguments of a call.\n\treturn Arr.some(\n\t\tArr.unfold(0, (index) => {\n\t\t\tconst visit = queue[index]\n\t\t\tif (visit === undefined) {\n\t\t\t\treturn Option.none()\n\t\t\t}\n\t\t\tif (visit.depth > 0 && visit.state === pattern.length) {\n\t\t\t\treturn Option.some([true, queue.length] as const)\n\t\t\t}\n\t\t\tArr.forEach(next(visit), (found) => {\n\t\t\t\tqueue.push(found)\n\t\t\t})\n\t\t\treturn Option.some([false, index + 1] as const)\n\t\t}),\n\t\t(match) => match,\n\t)\n}\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nmnmcc/ability",
3
- "version": "2.1.1",
3
+ "version": "2.1.2",
4
4
  "description": "Typed, composable authorization policies for Effect programs.",
5
5
  "license": "Apache-2.0",
6
6
  "type": "module",
package/src/Condition.ts CHANGED
@@ -691,11 +691,15 @@ type PathEnd<
691
691
  * names them. For a union of types, a part must be in each of them.
692
692
  */
693
693
  type PathCheck<T, F extends string, R extends PathRules> = [T] extends [object]
694
- ? PathWalk<T, F, "", R, []> extends true
695
- ? F
696
- : // The names come from parts of `F`. Inference must not find `F` in
697
- // them, so a call gets its `F` from the path that it gives.
698
- NoInfer<PathWalk<T, F, "", R, []>>
694
+ ? // Each path of a union of paths, as of a field typed `FieldSelector`,
695
+ // is checked by itself.
696
+ F extends unknown
697
+ ? PathWalk<T, F, "", R, []> extends true
698
+ ? F
699
+ : // The names come from parts of `F`. Inference must not find `F`
700
+ // in them, so a call gets its `F` from the path that it gives.
701
+ NoInfer<PathWalk<T, F, "", R, []>>
702
+ : never
699
703
  : never
700
704
 
701
705
  /**
@@ -1445,9 +1449,11 @@ export type PlainCondition = Condition<any>
1445
1449
  * The Schema of a condition with no predicate. It checks the shape only. Use
1446
1450
  * `Policy.schema` to check the field names against a domain too.
1447
1451
  *
1448
- * The decoder takes at most 500 levels of conditions, and at most 1000000
1449
- * objects and array items, with a shared object counted for each place. It
1450
- * reads only the own data fields of the input.
1452
+ * The decoder takes the default limits: at most `conditionNodes` nodes,
1453
+ * 1000000, and `jsonNodes` objects and array items, 10000000, with a shared
1454
+ * object counted for each place. It stops at the first one past a limit. It
1455
+ * takes any number of levels, and it reads only the own data fields of the
1456
+ * input. Use `Policy.schema` for the limits of a domain.
1451
1457
  *
1452
1458
  * @stability stable
1453
1459
  * @category schemas
package/src/Policy.ts CHANGED
@@ -267,12 +267,17 @@ type KindOfTarget<T> = T extends { readonly kind: infer K extends string }
267
267
  * @category utility types
268
268
  * @since 2.1.0
269
269
  */
270
- export type CheckField<D, T> = T extends {
271
- readonly field: infer F extends string
272
- }
273
- ? {
274
- readonly field: Condition.SelectPath<Domain.DataOf<D, KindOfTarget<T>>, F>
275
- }
270
+ export type CheckField<D, T> = T extends { readonly field?: infer F }
271
+ ? [Exclude<F, undefined>] extends [never]
272
+ ? unknown
273
+ : {
274
+ // A field that the target may leave out is checked too, when it is
275
+ // there.
276
+ readonly field?: Condition.SelectPath<
277
+ Domain.DataOf<D, KindOfTarget<T>>,
278
+ Exclude<F, undefined> & string
279
+ >
280
+ }
276
281
  : unknown
277
282
 
278
283
  /**
@@ -905,8 +910,8 @@ export type ResidualReads<D, K> = Condition.Reads<
905
910
  *
906
911
  * A policy tree may use the condition of a child two times, so a small policy
907
912
  * may give a very large condition. When the condition would have more than
908
- * 10000 nodes, this function throws an error. In an `Authorizer`, the error
909
- * is a defect, so the check fails.
913
+ * the `residualNodes` of the domain, 100000 by default, this function throws
914
+ * an error. In an `Authorizer`, the error is a defect, so the check fails.
910
915
  *
911
916
  * @stability stable
912
917
  * @category evaluation
@@ -1031,10 +1036,11 @@ export type EncodedNode =
1031
1036
  * every kind, action, field and principal field against the domain, and the
1032
1037
  * `meta` of each rule against the `meta` Schema of the domain.
1033
1038
  *
1034
- * The decoder takes at most 256 levels of policies and 500 levels of
1035
- * conditions, and at most 1000000 objects and array items, with a shared
1036
- * object counted for each place. It reads only the own data fields of the
1037
- * input.
1039
+ * The decoder takes the limits of the domain: at most `policyNodes` nodes
1040
+ * of the tree, `conditionNodes` nodes of each condition, and `jsonNodes`
1041
+ * objects and array items, with a shared object counted for each place. It
1042
+ * stops at the first one past a limit. It takes any number of levels, and it
1043
+ * reads only the own data fields of the input.
1038
1044
  *
1039
1045
  * @stability stable
1040
1046
  * @category schemas
@@ -18,6 +18,7 @@ import { hasProperty } from "effect/Predicate"
18
18
  import * as Schema from "effect/Schema"
19
19
  import * as SchemaAST from "effect/SchemaAST"
20
20
  import * as Limits from "./limits.ts"
21
+ import * as Trampoline from "./trampoline.ts"
21
22
 
22
23
  /** The kinds of values that a field may hold. */
23
24
  export interface Kinds {
@@ -234,18 +235,31 @@ const classStruct = (
234
235
  const alternatives = (
235
236
  ast: SchemaAST.AST,
236
237
  open: ReadonlySet<SchemaAST.AST>,
237
- ): ReadonlyArray<SchemaAST.AST> => {
238
+ ): ReadonlyArray<SchemaAST.AST> => alternativesOf({ ast, open })
239
+
240
+ /**
241
+ * The walk of {@link alternatives}. A union may hold a union, very many
242
+ * levels down, so it is a walk of the trampoline for an input of an unknown
243
+ * depth.
244
+ */
245
+ const alternativesOf = Trampoline.adaptive<
246
+ { readonly ast: SchemaAST.AST; readonly open: ReadonlySet<SchemaAST.AST> },
247
+ ReadonlyArray<SchemaAST.AST>
248
+ >((R, child) => ({ ast, open }, depth) => {
238
249
  switch (ast._tag) {
239
250
  case "Union":
240
- return Arr.flatMap(ast.types, (type) => alternatives(type, open))
251
+ return R.map(
252
+ R.forEach(ast.types, (type) => child({ ast: type, open }, depth + 1)),
253
+ Arr.flatten,
254
+ )
241
255
  case "Suspend":
242
256
  return open.has(ast)
243
- ? []
244
- : alternatives(ast.thunk(), new Set([...open, ast]))
257
+ ? R.succeed([])
258
+ : child({ ast: ast.thunk(), open: new Set([...open, ast]) }, depth + 1)
245
259
  default:
246
- return [ast]
260
+ return R.succeed([ast])
247
261
  }
248
- }
262
+ })
249
263
 
250
264
  /** The struct of a Schema: a struct, or the struct of a class. */
251
265
  const structOf = (ast: SchemaAST.AST): SchemaAST.Objects | undefined =>
@@ -667,7 +681,12 @@ const listOf = (
667
681
  if (visit === undefined) {
668
682
  return Option.none()
669
683
  }
670
- rest.push(...Arr.reverse(below(visit)))
684
+ // The visits go onto the stack one at a time: a spread into a call
685
+ // would pass one argument for each field, past the most arguments
686
+ // of a call.
687
+ Arr.forEach(Arr.reverse(below(visit)), (next) => {
688
+ rest.push(next)
689
+ })
671
690
  const field =
672
691
  visit.path.length === 0
673
692
  ? []
@@ -388,7 +388,9 @@ export const permittedFields = (
388
388
  const rules = policy.pipe(nodeOf, PolicyNodes.rulesOf)
389
389
  // The fields with names only: a list can not name every key of a record.
390
390
  const listed = Arr.filter(
391
- descriptor.catalog.fields,
391
+ // The list for the limit of the parts of a path of the domain, as the
392
+ // decision of each field reads it.
393
+ Catalog.fieldsOf(descriptor.catalog, policy.domain.limits.fieldPathParts),
392
394
  (field) =>
393
395
  field.listed &&
394
396
  Arr.every(field.path, (segment) => typeof segment === "string"),
@@ -201,11 +201,13 @@ export const matchesBelow = (
201
201
  /**
202
202
  * Tests if a pattern matches some field of a catalog, with at most `parts`
203
203
  * parts. A recursive Schema has no end of fields, so the search goes over the
204
- * pairs of a place of the catalog and the places of the match: each pair
205
- * comes one time, a level after a level, so the search is short, and it
206
- * keeps a queue, so a deep Schema does not fill the stack. The set of the
207
- * pairs is native, with keys that are numbers, and the set and the queue
208
- * belong to this call.
204
+ * pairs of a place of the catalog and one place of the match. There are at
205
+ * most the places of the catalog times the parts of the pattern of them, so
206
+ * the work is the steps of the catalog times the parts of the pattern, as the
207
+ * binding counts it. Each pair comes one time, a level after a level, so the
208
+ * search finds the shortest field first, and it keeps a queue, so a deep
209
+ * Schema does not fill the stack. The set of the pairs is native, and the set
210
+ * and the queue belong to this call.
209
211
  */
210
212
  export const fitsCatalog = (
211
213
  pattern: Pattern,
@@ -214,46 +216,52 @@ export const fitsCatalog = (
214
216
  ): boolean => {
215
217
  interface Visit {
216
218
  readonly branch: Catalog.Branch
217
- readonly states: States
219
+ readonly state: number
218
220
  readonly depth: number
219
221
  }
220
222
  const seen = new Set<string>()
221
- const next = (visit: Visit): ReadonlyArray<Visit> => {
222
- if (visit.states.length === 0 || visit.depth === parts) {
223
- return []
224
- }
225
- const steps: ReadonlyArray<readonly [Catalog.Segment, number]> = [
226
- ...visit.branch.children,
227
- ...(visit.branch.key === undefined || visit.branch.keyBranch === undefined
228
- ? []
229
- : [[visit.branch.key, visit.branch.keyBranch] as const]),
230
- ]
231
- return Arr.flatMap(steps, ([segment, id]) => {
232
- const states = step(pattern, visit.states, segment)
233
- const key = `${String(id)}|${states.join(",")}`
234
- if (states.length === 0 || seen.has(key)) {
223
+ const visits = (
224
+ id: number,
225
+ states: States,
226
+ depth: number,
227
+ ): ReadonlyArray<Visit> =>
228
+ Arr.flatMap(states, (state) => {
229
+ const key = `${String(id)}|${String(state)}`
230
+ if (seen.has(key)) {
235
231
  return []
236
232
  }
237
233
  seen.add(key)
238
- return [{ branch: catalog.branches[id]!, states, depth: visit.depth + 1 }]
234
+ return [{ branch: catalog.branches[id]!, state, depth }]
239
235
  })
236
+ const next = (visit: Visit): ReadonlyArray<Visit> => {
237
+ if (visit.depth === parts) {
238
+ return []
239
+ }
240
+ const branch = visit.branch
241
+ const steps: ReadonlyArray<readonly [Catalog.Segment, number]> =
242
+ branch.key === undefined || branch.keyBranch === undefined
243
+ ? branch.children
244
+ : [...branch.children, [branch.key, branch.keyBranch] as const]
245
+ return Arr.flatMap(steps, ([segment, id]) =>
246
+ visits(id, step(pattern, [visit.state], segment), visit.depth + 1),
247
+ )
240
248
  }
241
- const queue: Array<Visit> = [
242
- { branch: catalog.root, states: start(pattern), depth: 0 },
243
- ]
244
- // The walk stops at the first field that the pattern matches.
245
- const found = (visit: Visit) =>
246
- visit.depth > 0 && isDone(pattern, visit.states)
249
+ const queue: Array<Visit> = [...visits(catalog.root.id, start(pattern), 0)]
250
+ // The walk stops at the first field that the pattern matches. The visits
251
+ // go into the queue one at a time: a spread into a call would pass one
252
+ // argument for each, past the most arguments of a call.
247
253
  return Arr.some(
248
254
  Arr.unfold(0, (index) => {
249
255
  const visit = queue[index]
250
256
  if (visit === undefined) {
251
257
  return Option.none()
252
258
  }
253
- if (found(visit)) {
259
+ if (visit.depth > 0 && visit.state === pattern.length) {
254
260
  return Option.some([true, queue.length] as const)
255
261
  }
256
- queue.push(...next(visit))
262
+ Arr.forEach(next(visit), (found) => {
263
+ queue.push(found)
264
+ })
257
265
  return Option.some([false, index + 1] as const)
258
266
  }),
259
267
  (match) => match,