@nmnmcc/ability 1.0.0 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (127) hide show
  1. package/README.md +143 -87
  2. package/dist/Authorizer.d.ts +43 -47
  3. package/dist/Authorizer.d.ts.map +1 -1
  4. package/dist/Authorizer.js +5 -290
  5. package/dist/Authorizer.js.map +1 -1
  6. package/dist/Condition.d.ts +164 -332
  7. package/dist/Condition.d.ts.map +1 -1
  8. package/dist/Condition.js +75 -890
  9. package/dist/Condition.js.map +1 -1
  10. package/dist/Decision.d.ts +13 -13
  11. package/dist/Decision.d.ts.map +1 -1
  12. package/dist/Decision.js.map +1 -1
  13. package/dist/Domain.d.ts +10 -21
  14. package/dist/Domain.d.ts.map +1 -1
  15. package/dist/Domain.js +4 -62
  16. package/dist/Domain.js.map +1 -1
  17. package/dist/HttpApi.d.ts +31 -7
  18. package/dist/HttpApi.d.ts.map +1 -1
  19. package/dist/HttpApi.js +28 -11
  20. package/dist/HttpApi.js.map +1 -1
  21. package/dist/Limits.d.ts +114 -0
  22. package/dist/Limits.d.ts.map +1 -0
  23. package/dist/Limits.js +132 -0
  24. package/dist/Limits.js.map +1 -0
  25. package/dist/Policy.d.ts +274 -272
  26. package/dist/Policy.d.ts.map +1 -1
  27. package/dist/Policy.js +71 -1180
  28. package/dist/Policy.js.map +1 -1
  29. package/dist/Resource.d.ts +8 -181
  30. package/dist/Resource.d.ts.map +1 -1
  31. package/dist/Resource.js +8 -807
  32. package/dist/Resource.js.map +1 -1
  33. package/dist/index.d.ts +3 -4
  34. package/dist/index.js +4 -5
  35. package/dist/internal/algebra.js +267 -0
  36. package/dist/internal/algebra.js.map +1 -0
  37. package/dist/internal/analysis.js +245 -0
  38. package/dist/internal/analysis.js.map +1 -0
  39. package/dist/internal/authorizer.d.ts +30 -0
  40. package/dist/internal/authorizer.d.ts.map +1 -0
  41. package/dist/internal/authorizer.js +221 -0
  42. package/dist/internal/authorizer.js.map +1 -0
  43. package/dist/internal/bind.js +286 -0
  44. package/dist/internal/bind.js.map +1 -0
  45. package/dist/internal/catalog.d.ts +56 -0
  46. package/dist/internal/catalog.d.ts.map +1 -0
  47. package/dist/internal/catalog.js +292 -0
  48. package/dist/internal/catalog.js.map +1 -0
  49. package/dist/internal/decide.js +166 -0
  50. package/dist/internal/decide.js.map +1 -0
  51. package/dist/internal/domain.js +32 -0
  52. package/dist/internal/domain.js.map +1 -0
  53. package/dist/internal/json.js +134 -0
  54. package/dist/internal/json.js.map +1 -0
  55. package/dist/internal/limits.d.ts +14 -0
  56. package/dist/internal/limits.d.ts.map +1 -0
  57. package/dist/internal/limits.js +43 -0
  58. package/dist/internal/limits.js.map +1 -0
  59. package/dist/internal/nodes.d.ts +121 -0
  60. package/dist/internal/nodes.d.ts.map +1 -0
  61. package/dist/internal/nodes.js +277 -0
  62. package/dist/internal/nodes.js.map +1 -0
  63. package/dist/internal/patterns.js +110 -0
  64. package/dist/internal/patterns.js.map +1 -0
  65. package/dist/internal/policy-json.js +255 -0
  66. package/dist/internal/policy-json.js.map +1 -0
  67. package/dist/internal/policy-nodes.d.ts +61 -0
  68. package/dist/internal/policy-nodes.d.ts.map +1 -0
  69. package/dist/internal/policy-nodes.js +146 -0
  70. package/dist/internal/policy-nodes.js.map +1 -0
  71. package/dist/internal/policy.d.ts +42 -0
  72. package/dist/internal/policy.d.ts.map +1 -0
  73. package/dist/internal/policy.js +124 -0
  74. package/dist/internal/policy.js.map +1 -0
  75. package/dist/internal/read.js +39 -0
  76. package/dist/internal/read.js.map +1 -0
  77. package/dist/internal/resource.js +115 -0
  78. package/dist/internal/resource.js.map +1 -0
  79. package/dist/internal/rule.d.ts +26 -0
  80. package/dist/internal/rule.d.ts.map +1 -0
  81. package/dist/internal/rule.js +93 -0
  82. package/dist/internal/rule.js.map +1 -0
  83. package/dist/internal/search.d.ts +22 -0
  84. package/dist/internal/search.d.ts.map +1 -0
  85. package/dist/internal/search.js +274 -0
  86. package/dist/internal/search.js.map +1 -0
  87. package/dist/internal/suggest.js +46 -0
  88. package/dist/internal/suggest.js.map +1 -0
  89. package/package.json +4 -5
  90. package/src/Authorizer.ts +69 -585
  91. package/src/Condition.ts +339 -2095
  92. package/src/Decision.ts +6 -5
  93. package/src/Domain.ts +13 -70
  94. package/src/HttpApi.ts +75 -13
  95. package/src/Limits.ts +132 -0
  96. package/src/Policy.ts +386 -2772
  97. package/src/Resource.ts +14 -1555
  98. package/src/index.ts +3 -9
  99. package/src/internal/algebra.ts +482 -0
  100. package/src/internal/analysis.ts +478 -0
  101. package/src/internal/authorizer.ts +418 -0
  102. package/src/internal/bind.ts +596 -0
  103. package/src/internal/catalog.ts +534 -0
  104. package/src/internal/decide.ts +402 -0
  105. package/src/internal/domain.ts +66 -0
  106. package/src/internal/json.ts +260 -0
  107. package/src/internal/limits.ts +78 -0
  108. package/src/internal/nodes.ts +404 -0
  109. package/src/internal/patterns.ts +211 -0
  110. package/src/internal/policy-json.ts +484 -0
  111. package/src/internal/policy-nodes.ts +207 -0
  112. package/src/internal/policy.ts +264 -0
  113. package/src/internal/read.ts +55 -0
  114. package/src/internal/resource.ts +180 -0
  115. package/src/internal/rule.ts +181 -0
  116. package/src/internal/search.ts +541 -0
  117. package/src/internal/suggest.ts +104 -0
  118. package/dist/Principal.d.ts +0 -35
  119. package/dist/Principal.d.ts.map +0 -1
  120. package/dist/Principal.js +0 -37
  121. package/dist/Principal.js.map +0 -1
  122. package/dist/Rule.d.ts +0 -275
  123. package/dist/Rule.d.ts.map +0 -1
  124. package/dist/Rule.js +0 -257
  125. package/dist/Rule.js.map +0 -1
  126. package/src/Principal.ts +0 -41
  127. package/src/Rule.ts +0 -683
@@ -0,0 +1,134 @@
1
+ import { LimitExceeded, jsonNodes } from "./limits.js";
2
+ import { And, Compare, Exists, In, Not, Or, Under, always, isScalar, literal, never, principalRef, toJson as toJson$1 } from "./nodes.js";
3
+ import { readPart } from "./read.js";
4
+ import * as Result from "effect/Result";
5
+ import * as Data from "effect/Data";
6
+ import * as Arr from "effect/Array";
7
+ // src/internal/json.ts
8
+ /**
9
+ * The JSON form of a condition. The decoder is a plain walk with two limits:
10
+ * it goes at most `conditionDepth` levels down, and it reads at most
11
+ * `jsonNodes` objects and array items, with a shared object counted for each
12
+ * place. So no input can fill the stack or take a very long time. It makes
13
+ * each node with its constructor, and it reads only the own data fields of
14
+ * the input, as a condition reads a value.
15
+ */
16
+ /** Gives the JSON form of a condition with no predicate. See `Nodes.toJson`. */
17
+ const toJson = (node) => toJson$1(node);
18
+ /** A problem of an input, with the path of the problem. */
19
+ var Problem = class extends Data.TaggedError("Problem") {};
20
+ const problem = (path, message) => new Problem({
21
+ path,
22
+ message
23
+ });
24
+ /** Counts `count` more objects or items, and stops past the limit. */
25
+ const spend = (budget, count, path) => {
26
+ budget.used += count;
27
+ if (budget.used > 1e6) throw problem(path, `The input has more than ${String(jsonNodes)} objects and items. The most is ${String(jsonNodes)}.`);
28
+ };
29
+ /** Reads a field of the input. A field that is not plain data is a problem. */
30
+ const read = (input, name, path) => {
31
+ try {
32
+ return readPart(input, name);
33
+ } catch {
34
+ throw problem([...path, name], `The field "${name}" is not plain data`);
35
+ }
36
+ };
37
+ const readString = (input, name, path) => {
38
+ const value = read(input, name, path);
39
+ if (typeof value !== "string") throw problem([...path, name], `The field "${name}" must be a string`);
40
+ return value;
41
+ };
42
+ /** Reads the items of an array. */
43
+ const readList = (input, name, path, budget) => {
44
+ const value = read(input, name, path);
45
+ const at = [...path, name];
46
+ if (!Array.isArray(value)) throw problem(at, `The field "${name}" must be an array`);
47
+ const list = value;
48
+ spend(budget, list.length, at);
49
+ return Array.from({ length: list.length }, (_, index) => read(list, String(index), at));
50
+ };
51
+ const isJsonScalar = (value) => isScalar(value) && (typeof value !== "number" || Number.isFinite(value));
52
+ const decodeOperand = (input, path) => {
53
+ const tag = read(input, "_tag", path);
54
+ if (tag === "Literal") {
55
+ const own = typeof input === "object" && input !== null ? Object.getOwnPropertyDescriptor(input, "value") : void 0;
56
+ if (own === void 0 || "value" in own && own.value === void 0) throw problem([...path, "value"], "A fixed value is missing");
57
+ const value = read(input, "value", path);
58
+ if (!isJsonScalar(value)) throw problem([...path, "value"], "A fixed value must be a string, a finite number, a boolean or null");
59
+ return literal(value);
60
+ }
61
+ if (tag === "Principal") return principalRef(readString(input, "path", path));
62
+ throw problem([...path, "_tag"], "An operand must have the tag \"Literal\" or \"Principal\"");
63
+ };
64
+ const decodeOperands = (input, name, path, budget) => Arr.map(readList(input, name, path, budget), (operand, index) => decodeOperand(operand, [
65
+ ...path,
66
+ name,
67
+ index
68
+ ]));
69
+ const compareOperators = [
70
+ "eq",
71
+ "ne",
72
+ "lt",
73
+ "lte",
74
+ "gt",
75
+ "gte"
76
+ ];
77
+ const decodeNode = (input, path, depth, budget) => {
78
+ if (depth > 500) throw problem(path, `A condition has more than ${String(500)} levels. The most is ${String(500)}.`);
79
+ if (typeof input !== "object" || input === null) throw problem(path, "A condition node must be an object");
80
+ spend(budget, 1, path);
81
+ const children = (name) => Arr.map(readList(input, name, path, budget), (child, index) => decodeNode(child, [
82
+ ...path,
83
+ name,
84
+ index
85
+ ], depth + 1, budget));
86
+ try {
87
+ return makeNode(input, path, depth, budget, children);
88
+ } catch (error) {
89
+ if (error instanceof LimitExceeded) throw problem(path, error.message);
90
+ throw error;
91
+ }
92
+ };
93
+ const makeNode = (input, path, depth, budget, children) => {
94
+ switch (read(input, "_tag", path)) {
95
+ case "True": return always;
96
+ case "False": return never;
97
+ case "Compare": {
98
+ const op = readString(input, "op", path);
99
+ if (!compareOperators.includes(op)) throw problem([...path, "op"], `"${op}" is not a compare operator`);
100
+ return new Compare({
101
+ op,
102
+ field: readString(input, "field", path),
103
+ value: decodeOperand(read(input, "value", path), [...path, "value"])
104
+ });
105
+ }
106
+ case "In": return new In({
107
+ field: readString(input, "field", path),
108
+ values: decodeOperands(input, "values", path, budget)
109
+ });
110
+ case "Exists": return new Exists({ field: readString(input, "field", path) });
111
+ case "Under": return new Under({ prefix: decodeOperands(input, "prefix", path, budget) });
112
+ case "And": return new And(children("conditions"));
113
+ case "Or": return new Or(children("conditions"));
114
+ case "Not": return new Not(decodeNode(read(input, "condition", path), [...path, "condition"], depth + 1, budget));
115
+ default: throw problem([...path, "_tag"], "The node has no known tag");
116
+ }
117
+ };
118
+ /**
119
+ * Decodes the JSON form of a condition at a path, with the budget of a larger
120
+ * decode. It throws a {@link Problem}.
121
+ */
122
+ const decodeAt = (input, path, budget) => decodeNode(input, path, 1, budget);
123
+ /** Decodes the JSON form of a condition. */
124
+ const decode = (input) => {
125
+ try {
126
+ return Result.succeed(decodeNode(input, [], 1, { used: 0 }));
127
+ } catch (error) {
128
+ if (error instanceof Problem) return Result.fail(error);
129
+ throw error;
130
+ }
131
+ };
132
+ export { Problem, decode, decodeAt, problem, read, readList, readString, spend, toJson };
133
+
134
+ //# sourceMappingURL=json.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"json.js","names":["Nodes.toJson","Limits.jsonNodes","Nodes.isScalar","Nodes.literal","Nodes.principalRef","Limits.LimitExceeded","Nodes.always","Nodes.never","Nodes.Compare","Nodes.In","Nodes.Exists","Nodes.Under","Nodes.And","Nodes.Or","Nodes.Not"],"sources":["../../src/internal/json.ts"],"sourcesContent":["/**\n * The JSON form of a condition. The decoder is a plain walk with two limits:\n * it goes at most `conditionDepth` levels down, and it reads at most\n * `jsonNodes` objects and array items, with a shared object counted for each\n * place. So no input can fill the stack or 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 { readPart } from \"./read.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/** The count of the objects and items that one decode has read. It belongs to one decode. */\nexport interface Budget {\n\tused: number\n}\n\nexport const problem = (path: ReadonlyArray<PropertyKey>, message: string) =>\n\tnew Problem({ path, message })\n\n/** Counts `count` more objects or items, and stops past the limit. */\nexport const spend = (\n\tbudget: Budget,\n\tcount: number,\n\tpath: ReadonlyArray<PropertyKey>,\n): void => {\n\tbudget.used += count\n\tif (budget.used > Limits.jsonNodes) {\n\t\tthrow problem(\n\t\t\tpath,\n\t\t\t`The input has more than ${String(Limits.jsonNodes)} objects and items. The most is ${String(Limits.jsonNodes)}.`,\n\t\t)\n\t}\n}\n\n/** Reads a field of the input. A field that is not plain data is a problem. */\nexport const read = (\n\tinput: unknown,\n\tname: string,\n\tpath: ReadonlyArray<PropertyKey>,\n): unknown => {\n\ttry {\n\t\treturn readPart(input, name)\n\t} catch {\n\t\tthrow problem([...path, name], `The field \"${name}\" is not plain data`)\n\t}\n}\n\nexport const readString = (\n\tinput: unknown,\n\tname: string,\n\tpath: ReadonlyArray<PropertyKey>,\n): string => {\n\tconst value = read(input, name, path)\n\tif (typeof value !== \"string\") {\n\t\tthrow problem([...path, name], `The field \"${name}\" must be a string`)\n\t}\n\treturn value\n}\n\n/** Reads the items of an array. */\nexport const readList = (\n\tinput: unknown,\n\tname: string,\n\tpath: ReadonlyArray<PropertyKey>,\n\tbudget: Budget,\n): ReadonlyArray<unknown> => {\n\tconst value = read(input, name, path)\n\tconst at = [...path, name]\n\tif (!Array.isArray(value)) {\n\t\tthrow problem(at, `The field \"${name}\" must be an array`)\n\t}\n\tconst list: ReadonlyArray<unknown> = value\n\tspend(budget, list.length, at)\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), at),\n\t)\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\tpath: ReadonlyArray<PropertyKey>,\n): Nodes.Operand => {\n\tconst tag = read(input, \"_tag\", path)\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 problem([...path, \"value\"], \"A fixed value is missing\")\n\t\t}\n\t\tconst value = read(input, \"value\", path)\n\t\tif (!isJsonScalar(value)) {\n\t\t\tthrow problem(\n\t\t\t\t[...path, \"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(readString(input, \"path\", path))\n\t}\n\tthrow problem(\n\t\t[...path, \"_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\tpath: ReadonlyArray<PropertyKey>,\n\tbudget: Budget,\n): ReadonlyArray<Nodes.Operand> =>\n\tArr.map(readList(input, name, path, budget), (operand, index) =>\n\t\tdecodeOperand(operand, [...path, name, index]),\n\t)\n\nconst compareOperators: ReadonlyArray<string> = [\n\t\"eq\",\n\t\"ne\",\n\t\"lt\",\n\t\"lte\",\n\t\"gt\",\n\t\"gte\",\n]\n\nconst decodeNode = (\n\tinput: unknown,\n\tpath: ReadonlyArray<PropertyKey>,\n\tdepth: number,\n\tbudget: Budget,\n): Node => {\n\tif (depth > Limits.conditionDepth) {\n\t\tthrow problem(\n\t\t\tpath,\n\t\t\t`A condition has more than ${String(Limits.conditionDepth)} levels. The most is ${String(Limits.conditionDepth)}.`,\n\t\t)\n\t}\n\tif (typeof input !== \"object\" || input === null) {\n\t\tthrow problem(path, \"A condition node must be an object\")\n\t}\n\tspend(budget, 1, path)\n\tconst children = (name: string) =>\n\t\tArr.map(readList(input, name, path, budget), (child, index) =>\n\t\t\tdecodeNode(child, [...path, name, index], depth + 1, budget),\n\t\t)\n\ttry {\n\t\treturn makeNode(input, path, depth, budget, children)\n\t} catch (error) {\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.\n\t\tif (error instanceof Limits.LimitExceeded) {\n\t\t\tthrow problem(path, error.message)\n\t\t}\n\t\tthrow error\n\t}\n}\n\nconst makeNode = (\n\tinput: object,\n\tpath: ReadonlyArray<PropertyKey>,\n\tdepth: number,\n\tbudget: Budget,\n\tchildren: (name: string) => ReadonlyArray<Node>,\n): Node => {\n\tconst tag = read(input, \"_tag\", path)\n\tswitch (tag) {\n\t\tcase \"True\":\n\t\t\treturn Nodes.always\n\t\tcase \"False\":\n\t\t\treturn Nodes.never\n\t\tcase \"Compare\": {\n\t\t\tconst op = readString(input, \"op\", path)\n\t\t\tif (!compareOperators.includes(op)) {\n\t\t\t\tthrow problem([...path, \"op\"], `\"${op}\" is not a compare operator`)\n\t\t\t}\n\t\t\treturn new Nodes.Compare({\n\t\t\t\top: op as Nodes.CompareOperator,\n\t\t\t\tfield: readString(input, \"field\", path),\n\t\t\t\tvalue: decodeOperand(read(input, \"value\", path), [...path, \"value\"]),\n\t\t\t})\n\t\t}\n\t\tcase \"In\":\n\t\t\treturn new Nodes.In({\n\t\t\t\tfield: readString(input, \"field\", path),\n\t\t\t\tvalues: decodeOperands(input, \"values\", path, budget),\n\t\t\t})\n\t\tcase \"Exists\":\n\t\t\treturn new Nodes.Exists({ field: readString(input, \"field\", path) })\n\t\tcase \"Under\":\n\t\t\treturn new Nodes.Under({\n\t\t\t\tprefix: decodeOperands(input, \"prefix\", path, budget),\n\t\t\t})\n\t\tcase \"And\":\n\t\t\treturn new Nodes.And(children(\"conditions\"))\n\t\tcase \"Or\":\n\t\t\treturn new Nodes.Or(children(\"conditions\"))\n\t\tcase \"Not\":\n\t\t\treturn new Nodes.Not(\n\t\t\t\tdecodeNode(\n\t\t\t\t\tread(input, \"condition\", path),\n\t\t\t\t\t[...path, \"condition\"],\n\t\t\t\t\tdepth + 1,\n\t\t\t\t\tbudget,\n\t\t\t\t),\n\t\t\t)\n\t\tdefault:\n\t\t\tthrow problem([...path, \"_tag\"], \"The node has no known tag\")\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\tpath: ReadonlyArray<PropertyKey>,\n\tbudget: Budget,\n): Node => decodeNode(input, path, 1, budget)\n\n/** Decodes the JSON form of a condition. */\nexport const decode = (input: unknown): Result.Result<Node, Problem> => {\n\ttry {\n\t\treturn Result.succeed(decodeNode(input, [], 1, { used: 0 }))\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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"}
@@ -0,0 +1,14 @@
1
+ import * as Schema from "effect/Schema";
2
+ declare const LimitExceeded_base: Schema.Class<LimitExceeded, Schema.TaggedStruct<"LimitExceeded", {
3
+ readonly limit: Schema.String;
4
+ readonly size: Schema.Finite;
5
+ readonly max: Schema.Finite;
6
+ readonly message: Schema.String;
7
+ }>, import("effect/Cause").YieldableError>;
8
+ /**
9
+ * The error when an input goes past a limit. It names the limit, the size of
10
+ * the input, and the most that the limit takes.
11
+ */
12
+ export declare class LimitExceeded extends LimitExceeded_base {}
13
+ export {};
14
+ //# sourceMappingURL=limits.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"limits.d.ts","names":[],"sources":["../../src/internal/limits.ts"],"mappings":";;;;;;;;;;;qBAoDa,sBAAsB"}
@@ -0,0 +1,43 @@
1
+ import * as Schema from "effect/Schema";
2
+ /**
3
+ * The most nodes of one condition. A child that a condition uses in many
4
+ * places is counted for each place.
5
+ */
6
+ const conditionNodes = 1e5;
7
+ /** The most nodes of one residual condition. */
8
+ const residualNodes = 1e4;
9
+ /**
10
+ * The most nodes of a policy tree, with its rules and the nodes of their
11
+ * conditions. A part that the tree uses in many places is counted for each
12
+ * place.
13
+ */
14
+ const policyNodes = 1e5;
15
+ /** The most objects and arrays in one input to a decoder. */
16
+ const jsonNodes = 1e6;
17
+ /** The most fields in the catalog of one Schema. */
18
+ const catalogFields = 1e4;
19
+ /** The most steps of one search for a value. */
20
+ const searchSteps = 1e6;
21
+ /**
22
+ * The error when an input goes past a limit. It names the limit, the size of
23
+ * the input, and the most that the limit takes.
24
+ */
25
+ var LimitExceeded = class extends Schema.TaggedError("@nmnmcc/ability/Limits/LimitExceeded")("LimitExceeded", {
26
+ limit: Schema.String,
27
+ size: Schema.Finite,
28
+ max: Schema.Finite,
29
+ message: Schema.String
30
+ }) {};
31
+ /**
32
+ * Makes the error for a limit, with one form of message for every limit, as
33
+ * in "A condition has 100001 nodes. The most is 100000."
34
+ */
35
+ const exceeded = (thing, limit, unit, size, max) => LimitExceeded.make({
36
+ limit,
37
+ size,
38
+ max,
39
+ message: `${thing} has ${String(size)} ${unit}. The most is ${String(max)}.`
40
+ });
41
+ export { LimitExceeded, catalogFields, conditionNodes, exceeded, jsonNodes, policyNodes, residualNodes, searchSteps };
42
+
43
+ //# sourceMappingURL=limits.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"limits.js","names":[],"sources":["../../src/internal/limits.ts"],"sourcesContent":["/**\n * The one table of limits. Each limit keeps a walk, a decoder or a search\n * bounded, so no input can fill the stack or take a very long time. The\n * values are the same in the types and at run time where both have one, as\n * for the parts of a field path.\n */\nimport * as Schema from \"effect/Schema\"\n\n/** The most parts of a field path, as in `settings.model`. */\nexport const fieldPathParts = 5\n\n/** The most parts of a field pattern, as in `**.secret`. */\nexport const patternParts = 16\n\n/**\n * The most nodes of one condition. A child that a condition uses in many\n * places is counted for each place.\n */\nexport const conditionNodes = 100_000\n\n/** The most levels of one condition. */\nexport const conditionDepth = 500\n\n/** The most nodes of one residual condition. */\nexport const residualNodes = 10_000\n\n/** The most levels of a policy tree. */\nexport const policyDepth = 256\n\n/**\n * The most nodes of a policy tree, with its rules and the nodes of their\n * conditions. A part that the tree uses in many places is counted for each\n * place.\n */\nexport const policyNodes = 100_000\n\n/** The most objects and arrays in one input to a decoder. */\nexport const jsonNodes = 1_000_000\n\n/** The most fields in the catalog of one Schema. */\nexport const catalogFields = 10_000\n\n/** The most steps of one search for a value. */\nexport const searchSteps = 1_000_000\n\n/** The most fields that one search for a value may test. */\nexport const searchFields = 256\n\n/**\n * The error when an input goes past a limit. It names the limit, the size of\n * the input, and the most that the limit takes.\n */\nexport class LimitExceeded extends Schema.TaggedError<LimitExceeded>(\n\t\"@nmnmcc/ability/Limits/LimitExceeded\",\n)(\"LimitExceeded\", {\n\tlimit: Schema.String,\n\tsize: Schema.Finite,\n\tmax: Schema.Finite,\n\tmessage: Schema.String,\n}) {}\n\n/**\n * Makes the error for a limit, with one form of message for every limit, as\n * in \"A condition has 100001 nodes. The most is 100000.\"\n */\nexport const exceeded = (\n\tthing: string,\n\tlimit: string,\n\tunit: string,\n\tsize: number,\n\tmax: number,\n): LimitExceeded =>\n\tLimitExceeded.make({\n\t\tlimit,\n\t\tsize,\n\t\tmax,\n\t\tmessage: `${thing} has ${String(size)} ${unit}. The most is ${String(max)}.`,\n\t})\n"],"mappings":";;;;;AAkBA,MAAa,iBAAiB;;AAM9B,MAAa,gBAAgB;;;;;;AAU7B,MAAa,cAAc;;AAG3B,MAAa,YAAY;;AAGzB,MAAa,gBAAgB;;AAG7B,MAAa,cAAc;;;;;AAS3B,IAAa,gBAAb,cAAmC,OAAO,YACzC,sCACD,CAAC,CAAC,iBAAiB;CAClB,OAAO,OAAO;CACd,MAAM,OAAO;CACb,KAAK,OAAO;CACZ,SAAS,OAAO;AACjB,CAAC,CAAC,CAAC,CAAC;;;;;AAMJ,MAAa,YACZ,OACA,OACA,MACA,MACA,QAEA,cAAc,KAAK;CAClB;CACA;CACA;CACA,SAAS,GAAG,MAAM,OAAO,OAAO,IAAI,EAAE,GAAG,KAAK,gBAAgB,OAAO,GAAG,EAAE;AAC3E,CAAC"}
@@ -0,0 +1,121 @@
1
+ import * as Effect from "effect/Effect";
2
+ import * as Data from "effect/Data";
3
+ /** A value that a condition can compare. */
4
+ export type Scalar = string | number | boolean | null;
5
+ export type CompareOperator = "eq" | "ne" | "lt" | "lte" | "gt" | "gte";
6
+ /** A fixed value. */
7
+ export interface Literal {
8
+ readonly _tag: "Literal";
9
+ readonly value: Scalar;
10
+ }
11
+ /** A field of the principal. */
12
+ export interface PrincipalRef {
13
+ readonly _tag: "Principal";
14
+ readonly path: string;
15
+ }
16
+ export type Operand = Literal | PrincipalRef;
17
+ interface Facts {
18
+ readonly size: number;
19
+ readonly depth: number;
20
+ readonly opaque: boolean;
21
+ }
22
+ /** The base of every node. It keeps the facts. */
23
+ declare abstract class Base extends Data.Class {
24
+ #private;
25
+ /**
26
+ * The JSON form of the node, for `JSON.stringify`. A number that JSON can
27
+ * not hold, as `Infinity`, becomes an object that no decoder takes, in
28
+ * place of `null`, which would change the test.
29
+ */
30
+ toJSON(): unknown;
31
+ constructor(props: object, facts: Facts);
32
+ /** The number of nodes, with a shared child counted for each place. */
33
+ get size(): number;
34
+ /** The number of levels. */
35
+ get depth(): number;
36
+ /** The node has a predicate. */
37
+ get opaque(): boolean;
38
+ }
39
+ /** Always true. */
40
+ export declare class True extends Base {
41
+ readonly _tag = "True";
42
+ constructor();
43
+ }
44
+ /** Always false. */
45
+ export declare class False extends Base {
46
+ readonly _tag = "False";
47
+ constructor();
48
+ }
49
+ /** Compares a field with a value. */
50
+ export declare class Compare extends Base {
51
+ readonly _tag = "Compare";
52
+ readonly op: CompareOperator;
53
+ readonly field: string;
54
+ readonly value: Operand;
55
+ constructor(props: {
56
+ readonly op: CompareOperator;
57
+ readonly field: string;
58
+ readonly value: Operand;
59
+ });
60
+ }
61
+ /** True when a field is equal to one of the values. */
62
+ export declare class In extends Base {
63
+ readonly _tag = "In";
64
+ readonly field: string;
65
+ readonly values: ReadonlyArray<Operand>;
66
+ constructor(props: {
67
+ readonly field: string;
68
+ readonly values: ReadonlyArray<Operand>;
69
+ });
70
+ }
71
+ /** True when a field is not `null` and not missing. */
72
+ export declare class Exists extends Base {
73
+ readonly _tag = "Exists";
74
+ readonly field: string;
75
+ constructor(props: {
76
+ readonly field: string;
77
+ });
78
+ }
79
+ /** True when the path of the resource starts with the prefix. */
80
+ export declare class Under extends Base {
81
+ readonly _tag = "Under";
82
+ readonly prefix: ReadonlyArray<Operand>;
83
+ constructor(props: {
84
+ readonly prefix: ReadonlyArray<Operand>;
85
+ });
86
+ }
87
+ export declare class And extends Base {
88
+ readonly _tag = "And";
89
+ readonly conditions: ReadonlyArray<Node>;
90
+ constructor(conditions: ReadonlyArray<Node>);
91
+ }
92
+ export declare class Or extends Base {
93
+ readonly _tag = "Or";
94
+ readonly conditions: ReadonlyArray<Node>;
95
+ constructor(conditions: ReadonlyArray<Node>);
96
+ }
97
+ export declare class Not extends Base {
98
+ readonly _tag = "Not";
99
+ readonly condition: Node;
100
+ constructor(condition: Node);
101
+ }
102
+ /** The test of a predicate, with no types. */
103
+ export type Run = (value: unknown, principal: unknown) => Effect.Effect<boolean, unknown, unknown>;
104
+ /**
105
+ * A test that runs an `Effect`. Its test takes inputs of the type `never`, so
106
+ * no code can call it with a value of the wrong type: `Condition.evaluate`
107
+ * checks the input. Use {@link runOf} inside the library.
108
+ */
109
+ export declare class Predicate extends Base {
110
+ readonly _tag = "Predicate";
111
+ readonly name: string;
112
+ readonly run: (value: never, principal: never) => Effect.Effect<boolean, unknown, unknown>;
113
+ constructor(props: {
114
+ readonly name: string;
115
+ readonly run: Run;
116
+ });
117
+ }
118
+ /** A node of a condition. */
119
+ export type Node = True | False | Compare | In | Exists | Under | And | Or | Not | Predicate;
120
+ export {};
121
+ //# sourceMappingURL=nodes.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"nodes.d.ts","names":[],"sources":["../../src/internal/nodes.ts"],"mappings":";;;YA0BY;YAEA;;iBAGK;WACP;WACA,OAAO;;;iBAIA;WACP;WACA;;YAGE,UAAU,UAAU;UAwEtB;WACA;WACA;WACA;;;uBAoCK,aAAa,KAAK;;;;;;;EAMhC;EAMA,YAAY,eAAe,OAAO;;MAM9B;;MAKA;;MAKA;;;qBAMQ,aAAa;WAChB;EACT;;;qBAMY,cAAc;WACjB;EACT;;;qBAMY,gBAAgB;WACnB;WACQ,IAAI;WACJ;WACA,OAAO;EACxB,YAAY;aACF,IAAI;aACJ;aACA,OAAO;;;;qBAiBL,WAAW;WACd;WACQ;WACA,QAAQ,cAAc;EACvC,YAAY;aACF;aACA,QAAQ,cAAc;;;;qBAUpB,eAAe;WAClB;WACQ;EACjB,YAAY;aAAkB;;;;qBAMlB,cAAc;WACjB;WACQ,QAAQ,cAAc;EACvC,YAAY;aAAkB,QAAQ,cAAc;;;qBAgBxC,YAAY;WACf;WACQ,YAAY,cAAc;EAC3C,YAAY,YAAY,cAAc;;qBAM1B,WAAW;WACd;WACQ,YAAY,cAAc;EAC3C,YAAY,YAAY,cAAc;;qBAM1B,YAAY;WACf;WACQ,WAAW;EAC5B,YAAY,WAAW;;;YASZ,OACX,gBACA,uBACI,OAAO;;;;;;qBAOC,kBAAkB;WACrB;WACQ;WACA,MAChB,cACA,qBACI,OAAO;EACZ,YAAY;aAAkB;aAAuB,KAAK;;;;YAe/C,OACX,OAAO,QAAQ,UAAU,KAAK,SAAS,QAAQ,MAAM,KAAK,MAAM"}
@@ -0,0 +1,277 @@
1
+ import { __exportAll } from "../_virtual/_rolldown/runtime.js";
2
+ import { conditionNodes, exceeded } from "./limits.js";
3
+ import * as Data from "effect/Data";
4
+ import * as Arr from "effect/Array";
5
+ import { hasProperty } from "effect/Predicate";
6
+ // src/internal/nodes.ts
7
+ /**
8
+ * The nodes of a condition. Each node is a class, and only its constructor
9
+ * makes one, also when a condition comes from JSON. The constructor checks the
10
+ * data of the node and works out three facts once:
11
+ * - `size`: the number of nodes, with a shared child counted for each place;
12
+ * - `depth`: the number of levels;
13
+ * - `opaque`: the node has a predicate.
14
+ *
15
+ * The constructor stops a node that goes past `conditionNodes` or
16
+ * `conditionDepth`. So a walk over any condition visits at most
17
+ * `conditionNodes` nodes and goes at most `conditionDepth` levels down, and it
18
+ * needs no memo.
19
+ *
20
+ * The facts are private fields, so `Equal` and `Hash` do not read them: two
21
+ * nodes with the same data are equal.
22
+ */
23
+ var nodes_exports = /* @__PURE__ */ __exportAll({
24
+ And: () => And,
25
+ Compare: () => Compare,
26
+ Exists: () => Exists,
27
+ False: () => False,
28
+ In: () => In,
29
+ Not: () => Not,
30
+ Or: () => Or,
31
+ Predicate: () => Predicate,
32
+ True: () => True,
33
+ Under: () => Under,
34
+ always: () => always,
35
+ isNode: () => isNode,
36
+ isScalar: () => isScalar,
37
+ literal: () => literal,
38
+ never: () => never,
39
+ principalRef: () => principalRef,
40
+ runOf: () => runOf,
41
+ toJson: () => toJson,
42
+ toNode: () => toNode
43
+ });
44
+ const operands = Data.taggedEnum();
45
+ const literal = (value) => operands.Literal({ value });
46
+ const principalRef = (path) => operands.Principal({ path });
47
+ const compareOperators = [
48
+ "eq",
49
+ "ne",
50
+ "lt",
51
+ "lte",
52
+ "gt",
53
+ "gte"
54
+ ];
55
+ /** Tests if a value is a scalar. Any number is one, also `NaN`. */
56
+ const isScalar = (value) => value === null || typeof value === "string" || typeof value === "number" || typeof value === "boolean";
57
+ /**
58
+ * Checks a field path: a string with at most `fieldPathParts` parts. A longer
59
+ * path is never a field of a resource or of the principal.
60
+ */
61
+ const checkPath = (path) => {
62
+ if (typeof path !== "string") throw new Error("A field path must be a string");
63
+ const parts = path.split(".", 6).length;
64
+ if (parts > 5) throw exceeded("A field path", "fieldPathParts", "parts", parts, 5);
65
+ return path;
66
+ };
67
+ /** Checks an operand, and gives a new one with only its data. */
68
+ const checkOperand = (operand) => {
69
+ if (hasProperty(operand, "_tag")) {
70
+ if (operand._tag === "Literal" && hasProperty(operand, "value") && isScalar(operand.value)) return literal(operand.value);
71
+ if (operand._tag === "Principal" && hasProperty(operand, "path")) return principalRef(checkPath(operand.path));
72
+ }
73
+ throw new Error("An operand must be a fixed scalar value or a field of the principal");
74
+ };
75
+ const checkOperands = (values) => {
76
+ if (!Array.isArray(values)) throw new Error("A list of operands must be an array");
77
+ return Object.freeze(Arr.map(Array.from(values), checkOperand));
78
+ };
79
+ const leaf = {
80
+ size: 1,
81
+ depth: 1,
82
+ opaque: false
83
+ };
84
+ /** The facts of a node with children. It stops a node past the limits. */
85
+ const factsOf = (children) => {
86
+ const size = Arr.reduce(children, 1, (total, child) => total + child.size);
87
+ if (size > 1e5) throw exceeded("A condition", "conditionNodes", "nodes", size, conditionNodes);
88
+ const depth = 1 + Arr.reduce(children, 0, (top, child) => Math.max(top, child.depth));
89
+ if (depth > 500) throw exceeded("A condition", "conditionDepth", "levels", depth, 500);
90
+ return {
91
+ size,
92
+ depth,
93
+ opaque: Arr.some(children, (child) => child.opaque)
94
+ };
95
+ };
96
+ /** The base of every node. It keeps the facts. */
97
+ var Base = class extends Data.Class {
98
+ /**
99
+ * The JSON form of the node, for `JSON.stringify`. A number that JSON can
100
+ * not hold, as `Infinity`, becomes an object that no decoder takes, in
101
+ * place of `null`, which would change the test.
102
+ */
103
+ toJSON() {
104
+ return toJson(this);
105
+ }
106
+ #facts;
107
+ constructor(props, facts) {
108
+ super(props);
109
+ this.#facts = facts;
110
+ }
111
+ /** The number of nodes, with a shared child counted for each place. */
112
+ get size() {
113
+ return this.#facts.size;
114
+ }
115
+ /** The number of levels. */
116
+ get depth() {
117
+ return this.#facts.depth;
118
+ }
119
+ /** The node has a predicate. */
120
+ get opaque() {
121
+ return this.#facts.opaque;
122
+ }
123
+ };
124
+ /** Always true. */
125
+ var True = class extends Base {
126
+ _tag = "True";
127
+ constructor() {
128
+ super({}, leaf);
129
+ }
130
+ };
131
+ /** Always false. */
132
+ var False = class extends Base {
133
+ _tag = "False";
134
+ constructor() {
135
+ super({}, leaf);
136
+ }
137
+ };
138
+ /** Compares a field with a value. */
139
+ var Compare = class extends Base {
140
+ _tag = "Compare";
141
+ constructor(props) {
142
+ if (!compareOperators.includes(props.op)) throw new Error(`"${String(props.op)}" is not a compare operator`);
143
+ super({
144
+ op: props.op,
145
+ field: checkPath(props.field),
146
+ value: checkOperand(props.value)
147
+ }, leaf);
148
+ }
149
+ };
150
+ /** True when a field is equal to one of the values. */
151
+ var In = class extends Base {
152
+ _tag = "In";
153
+ constructor(props) {
154
+ super({
155
+ field: checkPath(props.field),
156
+ values: checkOperands(props.values)
157
+ }, leaf);
158
+ }
159
+ };
160
+ /** True when a field is not `null` and not missing. */
161
+ var Exists = class extends Base {
162
+ _tag = "Exists";
163
+ constructor(props) {
164
+ super({ field: checkPath(props.field) }, leaf);
165
+ }
166
+ };
167
+ /** True when the path of the resource starts with the prefix. */
168
+ var Under = class extends Base {
169
+ _tag = "Under";
170
+ constructor(props) {
171
+ super({ prefix: checkOperands(props.prefix) }, leaf);
172
+ }
173
+ };
174
+ const checkChildren = (conditions) => {
175
+ if (!Array.isArray(conditions)) throw new Error("The children of a node must be an array");
176
+ const children = Array.from(conditions);
177
+ if (!Arr.every(children, isNode)) throw notNode();
178
+ return Object.freeze(children);
179
+ };
180
+ var And = class extends Base {
181
+ _tag = "And";
182
+ constructor(conditions) {
183
+ const children = checkChildren(conditions);
184
+ super({ conditions: children }, factsOf(children));
185
+ }
186
+ };
187
+ var Or = class extends Base {
188
+ _tag = "Or";
189
+ constructor(conditions) {
190
+ const children = checkChildren(conditions);
191
+ super({ conditions: children }, factsOf(children));
192
+ }
193
+ };
194
+ var Not = class extends Base {
195
+ _tag = "Not";
196
+ constructor(condition) {
197
+ if (!isNode(condition)) throw notNode();
198
+ super({ condition }, factsOf([condition]));
199
+ }
200
+ };
201
+ /**
202
+ * A test that runs an `Effect`. Its test takes inputs of the type `never`, so
203
+ * no code can call it with a value of the wrong type: `Condition.evaluate`
204
+ * checks the input. Use {@link runOf} inside the library.
205
+ */
206
+ var Predicate = class extends Base {
207
+ _tag = "Predicate";
208
+ constructor(props) {
209
+ if (typeof props.name !== "string" || typeof props.run !== "function") throw new Error("A predicate needs a name and a test");
210
+ super({
211
+ name: props.name,
212
+ run: props.run
213
+ }, {
214
+ size: 1,
215
+ depth: 1,
216
+ opaque: true
217
+ });
218
+ }
219
+ };
220
+ /** Gives the test of a predicate, with no types. */
221
+ const runOf = (node) => node.run;
222
+ const operandJson = (operand) => operand._tag === "Literal" ? {
223
+ _tag: "Literal",
224
+ value: typeof operand.value === "number" && !Number.isFinite(operand.value) ? { nonFinite: String(operand.value) } : operand.value
225
+ } : {
226
+ _tag: "Principal",
227
+ path: operand.path
228
+ };
229
+ /** Gives the JSON form of a condition with no predicate. */
230
+ const toJson = (node) => {
231
+ switch (node._tag) {
232
+ case "True":
233
+ case "False": return { _tag: node._tag };
234
+ case "Compare": return {
235
+ _tag: "Compare",
236
+ op: node.op,
237
+ field: node.field,
238
+ value: operandJson(node.value)
239
+ };
240
+ case "In": return {
241
+ _tag: "In",
242
+ field: node.field,
243
+ values: Arr.map(node.values, operandJson)
244
+ };
245
+ case "Exists": return {
246
+ _tag: "Exists",
247
+ field: node.field
248
+ };
249
+ case "Under": return {
250
+ _tag: "Under",
251
+ prefix: Arr.map(node.prefix, operandJson)
252
+ };
253
+ case "And":
254
+ case "Or": return {
255
+ _tag: node._tag,
256
+ conditions: Arr.map(node.conditions, toJson)
257
+ };
258
+ case "Not": return {
259
+ _tag: "Not",
260
+ condition: toJson(node.condition)
261
+ };
262
+ case "Predicate": throw new Error(`The predicate "${node.name}" can not become JSON. A condition with a predicate has no JSON form.`);
263
+ }
264
+ };
265
+ /** Tests if a value is a node. Only a constructor of a node makes one. */
266
+ const isNode = (u) => u instanceof Base;
267
+ const notNode = () => /* @__PURE__ */ new Error("A condition must be made with a builder or a function of Condition. A plain object is not a condition");
268
+ /** Gives a value as a node, or throws when it is not a node. */
269
+ const toNode = (u) => {
270
+ if (!isNode(u)) throw notNode();
271
+ return u;
272
+ };
273
+ const always = new True();
274
+ const never = new False();
275
+ export { And, Compare, Exists, False, In, Not, Or, Predicate, True, Under, always, isNode, isScalar, literal, never, nodes_exports, principalRef, runOf, toJson, toNode };
276
+
277
+ //# sourceMappingURL=nodes.js.map