@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.
- package/README.md +2 -2
- package/dist/Condition.d.ts +6 -4
- package/dist/Condition.d.ts.map +1 -1
- package/dist/Condition.js +5 -3
- package/dist/Condition.js.map +1 -1
- package/dist/Policy.d.ts +10 -9
- package/dist/Policy.d.ts.map +1 -1
- package/dist/Policy.js +7 -6
- package/dist/Policy.js.map +1 -1
- package/dist/internal/catalog.d.ts.map +1 -1
- package/dist/internal/catalog.js +24 -6
- package/dist/internal/catalog.js.map +1 -1
- package/dist/internal/decide.js +2 -2
- package/dist/internal/decide.js.map +1 -1
- package/dist/internal/patterns.js +26 -26
- package/dist/internal/patterns.js.map +1 -1
- package/package.json +1 -1
- package/src/Condition.ts +14 -8
- package/src/Policy.ts +18 -12
- package/src/internal/catalog.ts +26 -7
- package/src/internal/decide.ts +3 -1
- package/src/internal/patterns.ts +37 -29
|
@@ -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
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
|
-
?
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
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
|
|
1449
|
-
* objects and array items, with a shared
|
|
1450
|
-
*
|
|
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
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
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
|
-
*
|
|
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
|
|
1035
|
-
*
|
|
1036
|
-
* object counted for each place. It
|
|
1037
|
-
*
|
|
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
|
package/src/internal/catalog.ts
CHANGED
|
@@ -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
|
|
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
|
-
:
|
|
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
|
-
|
|
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
|
? []
|
package/src/internal/decide.ts
CHANGED
|
@@ -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
|
-
|
|
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"),
|
package/src/internal/patterns.ts
CHANGED
|
@@ -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
|
|
205
|
-
*
|
|
206
|
-
*
|
|
207
|
-
*
|
|
208
|
-
*
|
|
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
|
|
219
|
+
readonly state: number
|
|
218
220
|
readonly depth: number
|
|
219
221
|
}
|
|
220
222
|
const seen = new Set<string>()
|
|
221
|
-
const
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
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]!,
|
|
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
|
-
|
|
243
|
-
|
|
244
|
-
//
|
|
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 (
|
|
259
|
+
if (visit.depth > 0 && visit.state === pattern.length) {
|
|
254
260
|
return Option.some([true, queue.length] as const)
|
|
255
261
|
}
|
|
256
|
-
|
|
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,
|