@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.
- package/README.md +143 -87
- package/dist/Authorizer.d.ts +43 -47
- package/dist/Authorizer.d.ts.map +1 -1
- package/dist/Authorizer.js +5 -290
- package/dist/Authorizer.js.map +1 -1
- package/dist/Condition.d.ts +164 -332
- package/dist/Condition.d.ts.map +1 -1
- package/dist/Condition.js +75 -890
- package/dist/Condition.js.map +1 -1
- package/dist/Decision.d.ts +13 -13
- package/dist/Decision.d.ts.map +1 -1
- package/dist/Decision.js.map +1 -1
- package/dist/Domain.d.ts +10 -21
- package/dist/Domain.d.ts.map +1 -1
- package/dist/Domain.js +4 -62
- package/dist/Domain.js.map +1 -1
- package/dist/HttpApi.d.ts +31 -7
- package/dist/HttpApi.d.ts.map +1 -1
- package/dist/HttpApi.js +28 -11
- package/dist/HttpApi.js.map +1 -1
- package/dist/Limits.d.ts +114 -0
- package/dist/Limits.d.ts.map +1 -0
- package/dist/Limits.js +132 -0
- package/dist/Limits.js.map +1 -0
- package/dist/Policy.d.ts +274 -272
- package/dist/Policy.d.ts.map +1 -1
- package/dist/Policy.js +71 -1180
- package/dist/Policy.js.map +1 -1
- package/dist/Resource.d.ts +8 -181
- package/dist/Resource.d.ts.map +1 -1
- package/dist/Resource.js +8 -807
- package/dist/Resource.js.map +1 -1
- package/dist/index.d.ts +3 -4
- package/dist/index.js +4 -5
- package/dist/internal/algebra.js +267 -0
- package/dist/internal/algebra.js.map +1 -0
- package/dist/internal/analysis.js +245 -0
- package/dist/internal/analysis.js.map +1 -0
- package/dist/internal/authorizer.d.ts +30 -0
- package/dist/internal/authorizer.d.ts.map +1 -0
- package/dist/internal/authorizer.js +221 -0
- package/dist/internal/authorizer.js.map +1 -0
- package/dist/internal/bind.js +286 -0
- package/dist/internal/bind.js.map +1 -0
- package/dist/internal/catalog.d.ts +56 -0
- package/dist/internal/catalog.d.ts.map +1 -0
- package/dist/internal/catalog.js +292 -0
- package/dist/internal/catalog.js.map +1 -0
- package/dist/internal/decide.js +166 -0
- package/dist/internal/decide.js.map +1 -0
- package/dist/internal/domain.js +32 -0
- package/dist/internal/domain.js.map +1 -0
- package/dist/internal/json.js +134 -0
- package/dist/internal/json.js.map +1 -0
- package/dist/internal/limits.d.ts +14 -0
- package/dist/internal/limits.d.ts.map +1 -0
- package/dist/internal/limits.js +43 -0
- package/dist/internal/limits.js.map +1 -0
- package/dist/internal/nodes.d.ts +121 -0
- package/dist/internal/nodes.d.ts.map +1 -0
- package/dist/internal/nodes.js +277 -0
- package/dist/internal/nodes.js.map +1 -0
- package/dist/internal/patterns.js +110 -0
- package/dist/internal/patterns.js.map +1 -0
- package/dist/internal/policy-json.js +255 -0
- package/dist/internal/policy-json.js.map +1 -0
- package/dist/internal/policy-nodes.d.ts +61 -0
- package/dist/internal/policy-nodes.d.ts.map +1 -0
- package/dist/internal/policy-nodes.js +146 -0
- package/dist/internal/policy-nodes.js.map +1 -0
- package/dist/internal/policy.d.ts +42 -0
- package/dist/internal/policy.d.ts.map +1 -0
- package/dist/internal/policy.js +124 -0
- package/dist/internal/policy.js.map +1 -0
- package/dist/internal/read.js +39 -0
- package/dist/internal/read.js.map +1 -0
- package/dist/internal/resource.js +115 -0
- package/dist/internal/resource.js.map +1 -0
- package/dist/internal/rule.d.ts +26 -0
- package/dist/internal/rule.d.ts.map +1 -0
- package/dist/internal/rule.js +93 -0
- package/dist/internal/rule.js.map +1 -0
- package/dist/internal/search.d.ts +22 -0
- package/dist/internal/search.d.ts.map +1 -0
- package/dist/internal/search.js +274 -0
- package/dist/internal/search.js.map +1 -0
- package/dist/internal/suggest.js +46 -0
- package/dist/internal/suggest.js.map +1 -0
- package/package.json +4 -5
- package/src/Authorizer.ts +69 -585
- package/src/Condition.ts +339 -2095
- package/src/Decision.ts +6 -5
- package/src/Domain.ts +13 -70
- package/src/HttpApi.ts +75 -13
- package/src/Limits.ts +132 -0
- package/src/Policy.ts +386 -2772
- package/src/Resource.ts +14 -1555
- package/src/index.ts +3 -9
- package/src/internal/algebra.ts +482 -0
- package/src/internal/analysis.ts +478 -0
- package/src/internal/authorizer.ts +418 -0
- package/src/internal/bind.ts +596 -0
- package/src/internal/catalog.ts +534 -0
- package/src/internal/decide.ts +402 -0
- package/src/internal/domain.ts +66 -0
- package/src/internal/json.ts +260 -0
- package/src/internal/limits.ts +78 -0
- package/src/internal/nodes.ts +404 -0
- package/src/internal/patterns.ts +211 -0
- package/src/internal/policy-json.ts +484 -0
- package/src/internal/policy-nodes.ts +207 -0
- package/src/internal/policy.ts +264 -0
- package/src/internal/read.ts +55 -0
- package/src/internal/resource.ts +180 -0
- package/src/internal/rule.ts +181 -0
- package/src/internal/search.ts +541 -0
- package/src/internal/suggest.ts +104 -0
- package/dist/Principal.d.ts +0 -35
- package/dist/Principal.d.ts.map +0 -1
- package/dist/Principal.js +0 -37
- package/dist/Principal.js.map +0 -1
- package/dist/Rule.d.ts +0 -275
- package/dist/Rule.d.ts.map +0 -1
- package/dist/Rule.js +0 -257
- package/dist/Rule.js.map +0 -1
- package/src/Principal.ts +0 -41
- package/src/Rule.ts +0 -683
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
import { __exportAll } from "../_virtual/_rolldown/runtime.js";
|
|
2
|
+
import { exceeded, policyNodes } from "./limits.js";
|
|
3
|
+
import { isNode as isNode$1 } from "./nodes.js";
|
|
4
|
+
import { isRule } from "./rule.js";
|
|
5
|
+
import * as Data from "effect/Data";
|
|
6
|
+
import * as Arr from "effect/Array";
|
|
7
|
+
// src/internal/policy-nodes.ts
|
|
8
|
+
/**
|
|
9
|
+
* The nodes of a policy tree. As with the nodes of a condition, each node is
|
|
10
|
+
* a class, only its constructor makes one, and it works out three facts once:
|
|
11
|
+
* - `size`: the number of nodes, with each rule and each node of its condition,
|
|
12
|
+
* and a shared child counted for each place;
|
|
13
|
+
* - `depth`: the number of levels of the tree;
|
|
14
|
+
* - `opaque`: a rule of the tree has a predicate.
|
|
15
|
+
*
|
|
16
|
+
* The constructor stops a tree past `policyNodes` or `policyDepth`. So a walk
|
|
17
|
+
* over any tree, such as a decision, needs no memo.
|
|
18
|
+
*/
|
|
19
|
+
var policy_nodes_exports = /* @__PURE__ */ __exportAll({
|
|
20
|
+
All: () => All,
|
|
21
|
+
Any: () => Any,
|
|
22
|
+
Ceiling: () => Ceiling,
|
|
23
|
+
FirstOf: () => FirstOf,
|
|
24
|
+
Rules: () => Rules,
|
|
25
|
+
isNode: () => isNode,
|
|
26
|
+
rulesOf: () => rulesOf
|
|
27
|
+
});
|
|
28
|
+
const algorithms = [
|
|
29
|
+
"denyOverrides",
|
|
30
|
+
"permitOverrides",
|
|
31
|
+
"firstApplicable",
|
|
32
|
+
"lastApplicable"
|
|
33
|
+
];
|
|
34
|
+
/** Checks the facts of a node against the limits. */
|
|
35
|
+
const checked = (facts) => {
|
|
36
|
+
if (facts.size > 1e5) throw exceeded("A policy tree", "policyNodes", "nodes", facts.size, policyNodes);
|
|
37
|
+
if (facts.depth > 256) throw exceeded("A policy tree", "policyDepth", "levels", facts.depth, 256);
|
|
38
|
+
return facts;
|
|
39
|
+
};
|
|
40
|
+
const factsOf = (children) => checked({
|
|
41
|
+
size: Arr.reduce(children, 1, (total, child) => total + child.size),
|
|
42
|
+
depth: 1 + Arr.reduce(children, 0, (top, child) => Math.max(top, child.depth)),
|
|
43
|
+
opaque: Arr.some(children, (child) => child.opaque)
|
|
44
|
+
});
|
|
45
|
+
/** The base of every node. It keeps the facts. */
|
|
46
|
+
var Base = class extends Data.Class {
|
|
47
|
+
#facts;
|
|
48
|
+
constructor(props, facts) {
|
|
49
|
+
super(props);
|
|
50
|
+
this.#facts = facts;
|
|
51
|
+
}
|
|
52
|
+
/** The number of nodes, with the rules and their conditions. */
|
|
53
|
+
get size() {
|
|
54
|
+
return this.#facts.size;
|
|
55
|
+
}
|
|
56
|
+
/** The number of levels of the tree. */
|
|
57
|
+
get depth() {
|
|
58
|
+
return this.#facts.depth;
|
|
59
|
+
}
|
|
60
|
+
/** A rule of the tree has a predicate. */
|
|
61
|
+
get opaque() {
|
|
62
|
+
return this.#facts.opaque;
|
|
63
|
+
}
|
|
64
|
+
};
|
|
65
|
+
/**
|
|
66
|
+
* The condition of a rule. A value that is not a node counts as one node
|
|
67
|
+
* here: the binding of the rule gives the issue for it, before any walk.
|
|
68
|
+
*/
|
|
69
|
+
const whenOf = (rule) => isNode$1(rule.when) ? rule.when : void 0;
|
|
70
|
+
/** A list of rules with a combining algorithm. */
|
|
71
|
+
var Rules = class extends Base {
|
|
72
|
+
_tag = "Rules";
|
|
73
|
+
constructor(props) {
|
|
74
|
+
if (!algorithms.includes(props.algorithm)) throw new Error(`"${String(props.algorithm)}" is not a combining algorithm`);
|
|
75
|
+
const rules = Object.freeze(Array.from(props.rules));
|
|
76
|
+
if (!Arr.every(rules, isRule)) throw new Error("A value in the list is not a rule");
|
|
77
|
+
const conditions = Arr.map(rules, whenOf);
|
|
78
|
+
super({
|
|
79
|
+
algorithm: props.algorithm,
|
|
80
|
+
rules
|
|
81
|
+
}, checked({
|
|
82
|
+
size: Arr.reduce(conditions, 1, (total, when) => total + 1 + (when?.size ?? 0)),
|
|
83
|
+
depth: 1,
|
|
84
|
+
opaque: Arr.some(conditions, (when) => when?.opaque ?? false)
|
|
85
|
+
}));
|
|
86
|
+
}
|
|
87
|
+
};
|
|
88
|
+
const checkChildren = (policies) => {
|
|
89
|
+
const children = Array.from(policies);
|
|
90
|
+
if (!Arr.every(children, isNode)) throw new Error("A child of a policy tree is not a node of a policy");
|
|
91
|
+
return Object.freeze(children);
|
|
92
|
+
};
|
|
93
|
+
/** Every policy must allow, and one deny is enough to deny. */
|
|
94
|
+
var All = class extends Base {
|
|
95
|
+
_tag = "All";
|
|
96
|
+
constructor(policies) {
|
|
97
|
+
const children = checkChildren(policies);
|
|
98
|
+
super({ policies: children }, factsOf(children));
|
|
99
|
+
}
|
|
100
|
+
};
|
|
101
|
+
/** One allow is enough. Without an allow, one deny gives a deny. */
|
|
102
|
+
var Any = class extends Base {
|
|
103
|
+
_tag = "Any";
|
|
104
|
+
constructor(policies) {
|
|
105
|
+
const children = checkChildren(policies);
|
|
106
|
+
super({ policies: children }, factsOf(children));
|
|
107
|
+
}
|
|
108
|
+
};
|
|
109
|
+
/** The first policy that allows or denies gives the answer. */
|
|
110
|
+
var FirstOf = class extends Base {
|
|
111
|
+
_tag = "FirstOf";
|
|
112
|
+
constructor(policies) {
|
|
113
|
+
const children = checkChildren(policies);
|
|
114
|
+
super({ policies: children }, factsOf(children));
|
|
115
|
+
}
|
|
116
|
+
};
|
|
117
|
+
/** A deny from `cap` always wins. An allow from `cap` does nothing. */
|
|
118
|
+
var Ceiling = class extends Base {
|
|
119
|
+
_tag = "Ceiling";
|
|
120
|
+
constructor(props) {
|
|
121
|
+
const [cap, policy] = checkChildren([props.cap, props.policy]);
|
|
122
|
+
super({
|
|
123
|
+
cap,
|
|
124
|
+
policy
|
|
125
|
+
}, factsOf([cap, policy]));
|
|
126
|
+
}
|
|
127
|
+
};
|
|
128
|
+
/** Tests if a value is a node of a policy tree. */
|
|
129
|
+
const isNode = (u) => u instanceof Base;
|
|
130
|
+
/**
|
|
131
|
+
* The rules of a tree, in order, once for each place. A walk that also goes
|
|
132
|
+
* into the condition of each rule takes the rules from this list first, so it
|
|
133
|
+
* does not go down the levels of the tree and of a condition at once.
|
|
134
|
+
*/
|
|
135
|
+
const rulesOf = (node) => {
|
|
136
|
+
switch (node._tag) {
|
|
137
|
+
case "Rules": return node.rules;
|
|
138
|
+
case "All":
|
|
139
|
+
case "Any":
|
|
140
|
+
case "FirstOf": return Arr.flatMap(node.policies, rulesOf);
|
|
141
|
+
case "Ceiling": return [...rulesOf(node.cap), ...rulesOf(node.policy)];
|
|
142
|
+
}
|
|
143
|
+
};
|
|
144
|
+
export { All, Any, Ceiling, FirstOf, Rules, isNode, policy_nodes_exports, rulesOf };
|
|
145
|
+
|
|
146
|
+
//# sourceMappingURL=policy-nodes.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"policy-nodes.js","names":["Limits.exceeded","Limits.policyNodes","Nodes.isNode","Rule.isRule"],"sources":["../../src/internal/policy-nodes.ts"],"sourcesContent":["/**\n * The nodes of a policy tree. As with the nodes of a condition, each node is\n * a class, only its constructor makes one, and it works out three facts once:\n * - `size`: the number of nodes, with each rule and each node of its condition,\n * and a shared child counted for each place;\n * - `depth`: the number of levels of the tree;\n * - `opaque`: a rule of the tree has a predicate.\n *\n * The constructor stops a tree past `policyNodes` or `policyDepth`. So a walk\n * over any tree, such as a decision, needs no memo.\n */\nimport * as Arr from \"effect/Array\"\nimport * as Data from \"effect/Data\"\nimport * as Rule from \"./rule.ts\"\nimport * as Limits from \"./limits.ts\"\nimport * as Nodes from \"./nodes.ts\"\n\nexport type Algorithm =\n\t\"denyOverrides\" | \"permitOverrides\" | \"firstApplicable\" | \"lastApplicable\"\n\nconst algorithms: ReadonlyArray<string> = [\n\t\"denyOverrides\",\n\t\"permitOverrides\",\n\t\"firstApplicable\",\n\t\"lastApplicable\",\n]\n\ninterface Facts {\n\treadonly size: number\n\treadonly depth: number\n\treadonly opaque: boolean\n}\n\n/** Checks the facts of a node against the limits. */\nconst checked = (facts: Facts): Facts => {\n\tif (facts.size > Limits.policyNodes) {\n\t\tthrow Limits.exceeded(\n\t\t\t\"A policy tree\",\n\t\t\t\"policyNodes\",\n\t\t\t\"nodes\",\n\t\t\tfacts.size,\n\t\t\tLimits.policyNodes,\n\t\t)\n\t}\n\tif (facts.depth > Limits.policyDepth) {\n\t\tthrow Limits.exceeded(\n\t\t\t\"A policy tree\",\n\t\t\t\"policyDepth\",\n\t\t\t\"levels\",\n\t\t\tfacts.depth,\n\t\t\tLimits.policyDepth,\n\t\t)\n\t}\n\treturn facts\n}\n\nconst factsOf = (children: ReadonlyArray<Node>): Facts =>\n\tchecked({\n\t\tsize: Arr.reduce(children, 1, (total, child) => total + child.size),\n\t\tdepth:\n\t\t\t1 + Arr.reduce(children, 0, (top, child) => Math.max(top, child.depth)),\n\t\topaque: Arr.some(children, (child) => child.opaque),\n\t})\n\n/** The base of every node. It keeps the facts. */\nabstract class Base extends Data.Class {\n\treadonly #facts: Facts\n\n\tconstructor(props: object, facts: Facts) {\n\t\tsuper(props as never)\n\t\tthis.#facts = facts\n\t}\n\n\t/** The number of nodes, with the rules and their conditions. */\n\tget size(): number {\n\t\treturn this.#facts.size\n\t}\n\n\t/** The number of levels of the tree. */\n\tget depth(): number {\n\t\treturn this.#facts.depth\n\t}\n\n\t/** A rule of the tree has a predicate. */\n\tget opaque(): boolean {\n\t\treturn this.#facts.opaque\n\t}\n}\n\n/**\n * The condition of a rule. A value that is not a node counts as one node\n * here: the binding of the rule gives the issue for it, before any walk.\n */\nconst whenOf = (rule: Rule.Rule): Nodes.Node | undefined =>\n\tNodes.isNode(rule.when) ? rule.when : undefined\n\n/** A list of rules with a combining algorithm. */\nexport class Rules extends Base {\n\treadonly _tag = \"Rules\"\n\tdeclare readonly algorithm: Algorithm\n\tdeclare readonly rules: ReadonlyArray<Rule.Rule>\n\tconstructor(props: {\n\t\treadonly algorithm: Algorithm\n\t\treadonly rules: ReadonlyArray<Rule.Rule>\n\t}) {\n\t\tif (!algorithms.includes(props.algorithm)) {\n\t\t\tthrow new Error(\n\t\t\t\t`\"${String(props.algorithm)}\" is not a combining algorithm`,\n\t\t\t)\n\t\t}\n\t\tconst rules = Object.freeze(Array.from(props.rules))\n\t\t// A value that is not a rule, such as a copy of a rule made with a\n\t\t// spread, may keep the type fields of another rule, and it does not\n\t\t// work in a generator. It is a mistake in the code, so it is a defect.\n\t\tif (!Arr.every(rules, Rule.isRule)) {\n\t\t\tthrow new Error(\"A value in the list is not a rule\")\n\t\t}\n\t\tconst conditions = Arr.map(rules, whenOf)\n\t\tsuper(\n\t\t\t{ algorithm: props.algorithm, rules },\n\t\t\tchecked({\n\t\t\t\tsize: Arr.reduce(\n\t\t\t\t\tconditions,\n\t\t\t\t\t1,\n\t\t\t\t\t(total, when) => total + 1 + (when?.size ?? 0),\n\t\t\t\t),\n\t\t\t\tdepth: 1,\n\t\t\t\topaque: Arr.some(conditions, (when) => when?.opaque ?? false),\n\t\t\t}),\n\t\t)\n\t}\n}\n\nconst checkChildren = (\n\tpolicies: ReadonlyArray<unknown>,\n): ReadonlyArray<Node> => {\n\tconst children = Array.from(policies)\n\tif (!Arr.every(children, isNode)) {\n\t\tthrow new Error(\"A child of a policy tree is not a node of a policy\")\n\t}\n\treturn Object.freeze(children)\n}\n\n/** Every policy must allow, and one deny is enough to deny. */\nexport class All extends Base {\n\treadonly _tag = \"All\"\n\tdeclare readonly policies: ReadonlyArray<Node>\n\tconstructor(policies: ReadonlyArray<Node>) {\n\t\tconst children = checkChildren(policies)\n\t\tsuper({ policies: children }, factsOf(children))\n\t}\n}\n\n/** One allow is enough. Without an allow, one deny gives a deny. */\nexport class Any extends Base {\n\treadonly _tag = \"Any\"\n\tdeclare readonly policies: ReadonlyArray<Node>\n\tconstructor(policies: ReadonlyArray<Node>) {\n\t\tconst children = checkChildren(policies)\n\t\tsuper({ policies: children }, factsOf(children))\n\t}\n}\n\n/** The first policy that allows or denies gives the answer. */\nexport class FirstOf extends Base {\n\treadonly _tag = \"FirstOf\"\n\tdeclare readonly policies: ReadonlyArray<Node>\n\tconstructor(policies: ReadonlyArray<Node>) {\n\t\tconst children = checkChildren(policies)\n\t\tsuper({ policies: children }, factsOf(children))\n\t}\n}\n\n/** A deny from `cap` always wins. An allow from `cap` does nothing. */\nexport class Ceiling extends Base {\n\treadonly _tag = \"Ceiling\"\n\tdeclare readonly cap: Node\n\tdeclare readonly policy: Node\n\tconstructor(props: { readonly cap: Node; readonly policy: Node }) {\n\t\tconst [cap, policy] = checkChildren([props.cap, props.policy])\n\t\tsuper({ cap, policy }, factsOf([cap!, policy!]))\n\t}\n}\n\n/** A node of a policy tree. */\nexport type Node = Rules | All | Any | FirstOf | Ceiling\n\n/** Tests if a value is a node of a policy tree. */\nexport const isNode = (u: unknown): u is Node => u instanceof Base\n\n/**\n * The rules of a tree, in order, once for each place. A walk that also goes\n * into the condition of each rule takes the rules from this list first, so it\n * does not go down the levels of the tree and of a condition at once.\n */\nexport const rulesOf = (node: Node): ReadonlyArray<Rule.Rule> => {\n\tswitch (node._tag) {\n\t\tcase \"Rules\":\n\t\t\treturn node.rules\n\t\tcase \"All\":\n\t\tcase \"Any\":\n\t\tcase \"FirstOf\":\n\t\t\treturn Arr.flatMap(node.policies, rulesOf)\n\t\tcase \"Ceiling\":\n\t\t\treturn [...rulesOf(node.cap), ...rulesOf(node.policy)]\n\t}\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { Any as Any$1 } from "../Domain.js";
|
|
2
|
+
import "./policy-nodes.js";
|
|
3
|
+
import { Any as Any$2, Node as Node$1 } from "../Policy.js";
|
|
4
|
+
import "effect/Context";
|
|
5
|
+
import * as Schema from "effect/Schema";
|
|
6
|
+
import * as Data from "effect/Data";
|
|
7
|
+
type Any = Any$2;
|
|
8
|
+
type Node = Node$1;
|
|
9
|
+
/**
|
|
10
|
+
* The class of every policy value. Two policies of one domain with the same
|
|
11
|
+
* tree are equal. As JSON, a policy is its tree.
|
|
12
|
+
*/
|
|
13
|
+
export declare class PolicyData<D extends Any$1 = any, X = never> extends Data.Class<{
|
|
14
|
+
readonly domain: D;
|
|
15
|
+
readonly node: Node;
|
|
16
|
+
}> {
|
|
17
|
+
#private;
|
|
18
|
+
/** Only in the types. */
|
|
19
|
+
readonly "~Opaque": () => X;
|
|
20
|
+
/**
|
|
21
|
+
* Only in the types. A policy reads the values and the principal of its
|
|
22
|
+
* domain, so a policy of one domain is not a policy of a wider domain.
|
|
23
|
+
*/
|
|
24
|
+
readonly "~Domain": (domain: D) => D;
|
|
25
|
+
toJSON(): unknown;
|
|
26
|
+
/** Tests if a value is a policy, by its private field. */
|
|
27
|
+
static is(u: unknown): u is Any;
|
|
28
|
+
}
|
|
29
|
+
declare const PolicyError_base: Schema.Class<PolicyError, Schema.TaggedStruct<"PolicyError", {
|
|
30
|
+
readonly issues: Schema.$Array<Schema.Struct<{
|
|
31
|
+
readonly path: Schema.$Array<Schema.Union<readonly [Schema.String, Schema.Finite]>>;
|
|
32
|
+
readonly message: Schema.String;
|
|
33
|
+
}>>;
|
|
34
|
+
readonly message: Schema.String;
|
|
35
|
+
}>, import("effect/Cause").YieldableError>;
|
|
36
|
+
/**
|
|
37
|
+
* The error when a rule does not fit its domain. It has one issue for each
|
|
38
|
+
* problem, with its path.
|
|
39
|
+
*/
|
|
40
|
+
export declare class PolicyError extends PolicyError_base {}
|
|
41
|
+
export {};
|
|
42
|
+
//# sourceMappingURL=policy.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"policy.d.ts","names":["Policy.Any","Policy.Node","Domain.Any"],"sources":["../../src/internal/policy.ts"],"mappings":";;;;;;KAsBK,MAAMA;KACN,OAAOC;;;;;qBAOC,WACZ,UAAUC,aACV,mBACS,KAAK;WACL,QAAQ;WACR,MAAM;;;;4BAOmB;;;;;uBAKL,QAAQ,MAAM;EAE3C;;SAKO,GAAG,aAAa,KAAK;;;;;;;;;;;;;qBAehB,oBAAoB"}
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
import { toNode } from "./nodes.js";
|
|
2
|
+
import { provideContext as provideContext$1 } from "../Condition.js";
|
|
3
|
+
import { treeIssues } from "./bind.js";
|
|
4
|
+
import { builder, make } from "./rule.js";
|
|
5
|
+
import { Ceiling, Rules, policy_nodes_exports, rulesOf } from "./policy-nodes.js";
|
|
6
|
+
import { decode, encode, syncMeta, toJson } from "./policy-json.js";
|
|
7
|
+
import * as Effect from "effect/Effect";
|
|
8
|
+
import * as Schema from "effect/Schema";
|
|
9
|
+
import * as Data from "effect/Data";
|
|
10
|
+
import * as Arr from "effect/Array";
|
|
11
|
+
import { dual } from "effect/Function";
|
|
12
|
+
import * as SchemaIssue from "effect/SchemaIssue";
|
|
13
|
+
import * as SchemaTransformation from "effect/SchemaTransformation";
|
|
14
|
+
import * as Option from "effect/Option";
|
|
15
|
+
// src/internal/policy.ts
|
|
16
|
+
/**
|
|
17
|
+
* The class of every policy value. Two policies of one domain with the same
|
|
18
|
+
* tree are equal. As JSON, a policy is its tree.
|
|
19
|
+
*/
|
|
20
|
+
var PolicyData = class extends Data.Class {
|
|
21
|
+
#policy = true;
|
|
22
|
+
toJSON() {
|
|
23
|
+
return toJson(this.node, syncMeta(this.domain.meta));
|
|
24
|
+
}
|
|
25
|
+
/** Tests if a value is a policy, by its private field. */
|
|
26
|
+
static is(u) {
|
|
27
|
+
return typeof u === "object" && u !== null && #policy in u;
|
|
28
|
+
}
|
|
29
|
+
};
|
|
30
|
+
/** The node of a policy, with no types, for the code below. */
|
|
31
|
+
const nodeOf = (policy) => policy.node;
|
|
32
|
+
const fromNode = (domain, node) => new PolicyData({
|
|
33
|
+
domain,
|
|
34
|
+
node
|
|
35
|
+
});
|
|
36
|
+
/**
|
|
37
|
+
* The error when a rule does not fit its domain. It has one issue for each
|
|
38
|
+
* problem, with its path.
|
|
39
|
+
*/
|
|
40
|
+
var PolicyError = class extends Schema.TaggedError("@nmnmcc/ability/Policy/PolicyError")("PolicyError", {
|
|
41
|
+
issues: Schema.Array(Schema.Struct({
|
|
42
|
+
path: Schema.Array(Schema.Union([Schema.String, Schema.Finite])),
|
|
43
|
+
message: Schema.String
|
|
44
|
+
})),
|
|
45
|
+
message: Schema.String
|
|
46
|
+
}) {};
|
|
47
|
+
/** Makes a policy from a list of rules, and binds them to the domain. */
|
|
48
|
+
const fromRules = (domain, values, options) => {
|
|
49
|
+
const node = new Rules({
|
|
50
|
+
algorithm: options?.algorithm ?? "denyOverrides",
|
|
51
|
+
rules: Array.from(values)
|
|
52
|
+
});
|
|
53
|
+
if (Arr.some(node.rules, (value) => {
|
|
54
|
+
const origin = value.domain;
|
|
55
|
+
return origin !== void 0 && origin !== domain;
|
|
56
|
+
})) throw new globalThis.Error("A rule comes from the builder of another domain");
|
|
57
|
+
const issues = treeIssues(domain, node);
|
|
58
|
+
if (issues.length > 0) throw PolicyError.make({
|
|
59
|
+
issues: Arr.map(issues, (issue) => ({
|
|
60
|
+
path: Arr.map(issue.path, (part) => typeof part === "number" ? part : String(part)),
|
|
61
|
+
message: issue.issue
|
|
62
|
+
})),
|
|
63
|
+
message: `A rule does not fit the domain: ${issues.map((issue) => issue.issue).join("; ")}`
|
|
64
|
+
});
|
|
65
|
+
return fromNode(domain, node);
|
|
66
|
+
};
|
|
67
|
+
/** Makes a policy from a generator function that gives rules. */
|
|
68
|
+
const define = (domain, options) => (body) => {
|
|
69
|
+
const iterator = body(builder(domain));
|
|
70
|
+
const collected = Arr.unfold(iterator.next(), (state) => state.done === true ? Option.none() : Option.some([state.value, iterator.next(state.value)]));
|
|
71
|
+
return fromRules(domain, collected, options);
|
|
72
|
+
};
|
|
73
|
+
const sameDomain = (policies) => {
|
|
74
|
+
if (!Arr.every(policies, isPolicy)) throw new globalThis.Error("A value in the list is not a policy");
|
|
75
|
+
const domain = policies[0].domain;
|
|
76
|
+
if (!Arr.every(policies, (policy) => policy.domain === domain)) throw new globalThis.Error("Policies from two domains can not be put together");
|
|
77
|
+
return domain;
|
|
78
|
+
};
|
|
79
|
+
const combine = (tag) => (policies) => fromNode(sameDomain(policies), new policy_nodes_exports[tag](Arr.map(policies, nodeOf)));
|
|
80
|
+
/** Puts a limit on a policy. A deny from `cap` always wins. */
|
|
81
|
+
const ceiling = dual(2, (policy, cap) => fromNode(sameDomain([policy, cap]), new Ceiling({
|
|
82
|
+
cap: nodeOf(cap),
|
|
83
|
+
policy: nodeOf(policy)
|
|
84
|
+
})));
|
|
85
|
+
/**
|
|
86
|
+
* Gives the services to the predicates of a tree. A node with none stays. The
|
|
87
|
+
* conditions get the services first, from a flat list of the rules, so the
|
|
88
|
+
* walk does not go down the levels of the tree and of a condition at once.
|
|
89
|
+
*/
|
|
90
|
+
const provideNode = (root, context) => {
|
|
91
|
+
const provided = new Map(Arr.map(rulesOf(root), (value) => [value, value.when === void 0 || !toNode(value.when).opaque ? value : make({
|
|
92
|
+
...value,
|
|
93
|
+
when: provideContext$1(value.when, context)
|
|
94
|
+
})]));
|
|
95
|
+
const provide = (node) => {
|
|
96
|
+
if (!node.opaque) return node;
|
|
97
|
+
switch (node._tag) {
|
|
98
|
+
case "Rules": return new Rules({
|
|
99
|
+
algorithm: node.algorithm,
|
|
100
|
+
rules: Arr.map(node.rules, (value) => provided.get(value) ?? value)
|
|
101
|
+
});
|
|
102
|
+
case "All":
|
|
103
|
+
case "Any":
|
|
104
|
+
case "FirstOf": return new policy_nodes_exports[node._tag](Arr.map(node.policies, provide));
|
|
105
|
+
case "Ceiling": return new Ceiling({
|
|
106
|
+
cap: provide(node.cap),
|
|
107
|
+
policy: provide(node.policy)
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
};
|
|
111
|
+
return provide(root);
|
|
112
|
+
};
|
|
113
|
+
/** Gives the services in `context` to every predicate of a policy. */
|
|
114
|
+
const provideContext = dual(2, (policy, context) => fromNode(policy.domain, provideNode(nodeOf(policy), context)));
|
|
115
|
+
/** Tests if a value is a policy. */
|
|
116
|
+
const isPolicy = (u) => PolicyData.is(u);
|
|
117
|
+
/** The Schema of the policies of a domain with no predicates. */
|
|
118
|
+
const schema = (domain) => Schema.Unknown.pipe(Schema.decodeTo(Schema.declare((u) => isPolicy(u) && u.domain === domain && !nodeOf(u).opaque).check(Schema.makeFilter((policy) => treeIssues(domain, nodeOf(policy)))), SchemaTransformation.transformEffect({
|
|
119
|
+
decode: (input, options) => decode(input, domain).pipe(Effect.map((node) => fromNode(domain, node)), Effect.mapError((problem) => new SchemaIssue.Pointer(problem.path, problem.issue ?? new SchemaIssue.InvalidValue({ message: problem.message }, input, options)))),
|
|
120
|
+
encode: (policy) => Effect.mapError(encode(nodeOf(policy), domain.meta), (error) => error.issue)
|
|
121
|
+
})));
|
|
122
|
+
export { PolicyData, PolicyError, ceiling, combine, define, fromNode, isPolicy, nodeOf, provideContext, schema };
|
|
123
|
+
|
|
124
|
+
//# sourceMappingURL=policy.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"policy.js","names":["PolicyJson.toJson","PolicyJson.syncMeta","PolicyNodes.Rules","Bind.treeIssues","RuleData.builder","PolicyNodes","PolicyNodes.Ceiling","PolicyNodes.rulesOf","Nodes.toNode","RuleData.make","Condition.provideContext","PolicyJson.decode","PolicyJson.encode"],"sources":["../../src/internal/policy.ts"],"sourcesContent":["/**\n * The values of policies: the class of a policy, the constructors, the\n * combinators and the Schema. The types are in `Policy.ts`.\n */\nimport type * as Context from \"effect/Context\"\nimport * as Data from \"effect/Data\"\nimport * as Arr from \"effect/Array\"\nimport * as Effect from \"effect/Effect\"\nimport { dual } from \"effect/Function\"\nimport * as Option from \"effect/Option\"\nimport * as Schema from \"effect/Schema\"\nimport * as SchemaIssue from \"effect/SchemaIssue\"\nimport * as SchemaTransformation from \"effect/SchemaTransformation\"\nimport * as Condition from \"../Condition.ts\"\nimport type * as Domain from \"../Domain.ts\"\nimport type * as Policy from \"../Policy.ts\"\nimport * as Bind from \"./bind.ts\"\nimport * as Nodes from \"./nodes.ts\"\nimport * as PolicyJson from \"./policy-json.ts\"\nimport * as PolicyNodes from \"./policy-nodes.ts\"\nimport * as RuleData from \"./rule.ts\"\n\ntype Any = Policy.Any\ntype Node = Policy.Node\ntype Options = Policy.Options\n\n/**\n * The class of every policy value. Two policies of one domain with the same\n * tree are equal. As JSON, a policy is its tree.\n */\nexport class PolicyData<\n\tD extends Domain.Any = any,\n\tX = never,\n> extends Data.Class<{\n\treadonly domain: D\n\treadonly node: Node\n}> {\n\t// A private field makes the type of a policy its own: a spread of a\n\t// policy, or an object made by hand, is not a policy. `Equal` does not\n\t// read it.\n\treadonly #policy = true\n\t/** Only in the types. */\n\tdeclare readonly \"~Opaque\": () => X\n\t/**\n\t * Only in the types. A policy reads the values and the principal of its\n\t * domain, so a policy of one domain is not a policy of a wider domain.\n\t */\n\tdeclare readonly \"~Domain\": (domain: D) => D\n\n\ttoJSON(): unknown {\n\t\treturn PolicyJson.toJson(this.node, PolicyJson.syncMeta(this.domain.meta))\n\t}\n\n\t/** Tests if a value is a policy, by its private field. */\n\tstatic is(u: unknown): u is Any {\n\t\treturn typeof u === \"object\" && u !== null && #policy in u\n\t}\n}\n\n/** The node of a policy, with no types, for the code below. */\nexport const nodeOf = (policy: Any): PolicyNodes.Node => policy.node as never\n\nexport const fromNode = (domain: Domain.Any, node: PolicyNodes.Node): any =>\n\tnew PolicyData({ domain, node })\n\n/**\n * The error when a rule does not fit its domain. It has one issue for each\n * problem, with its path.\n */\nexport class PolicyError extends Schema.TaggedError<PolicyError>(\n\t\"@nmnmcc/ability/Policy/PolicyError\",\n)(\"PolicyError\", {\n\tissues: Schema.Array(\n\t\tSchema.Struct({\n\t\t\tpath: Schema.Array(Schema.Union([Schema.String, Schema.Finite])),\n\t\t\tmessage: Schema.String,\n\t\t}),\n\t),\n\tmessage: Schema.String,\n}) {}\n\n/** Makes a policy from a list of rules, and binds them to the domain. */\nconst fromRules = (\n\tdomain: Domain.Any,\n\tvalues: Iterable<unknown>,\n\toptions?: Options,\n): Any => {\n\t// The constructor takes only rules, and it stops a list past the limits.\n\tconst node = new PolicyNodes.Rules({\n\t\talgorithm: options?.algorithm ?? \"denyOverrides\",\n\t\trules: Array.from(values) as ReadonlyArray<RuleData.Rule>,\n\t})\n\t// A rule from a builder of another domain is a mistake in the code, so it\n\t// is a defect. Two domains may have the same types, so the types do not\n\t// find it.\n\tif (\n\t\tArr.some(node.rules, (value) => {\n\t\t\tconst origin = value.domain\n\t\t\treturn origin !== undefined && origin !== domain\n\t\t})\n\t) {\n\t\tthrow new globalThis.Error(\n\t\t\t\"A rule comes from the builder of another domain\",\n\t\t)\n\t}\n\t// Every rule is bound to the domain here, also a rule from `Policy.rule`.\n\tconst issues = Bind.treeIssues(domain, node)\n\tif (issues.length > 0) {\n\t\tthrow PolicyError.make({\n\t\t\tissues: Arr.map(issues, (issue) => ({\n\t\t\t\tpath: Arr.map(issue.path, (part) =>\n\t\t\t\t\ttypeof part === \"number\" ? part : String(part),\n\t\t\t\t),\n\t\t\t\tmessage: issue.issue,\n\t\t\t})),\n\t\t\tmessage: `A rule does not fit the domain: ${issues.map((issue) => issue.issue).join(\"; \")}`,\n\t\t})\n\t}\n\treturn fromNode(domain, node)\n}\n\n/** Makes a policy from a generator function that gives rules. */\nexport const define =\n\t(domain: Domain.Any, options?: Options) =>\n\t(body: (builder: unknown) => Generator<unknown, unknown, unknown>): Any => {\n\t\tconst iterator = body(RuleData.builder(domain))\n\t\t// Each rule goes back into the generator, as `yield*` gives it.\n\t\tconst collected = Arr.unfold(iterator.next(), (state) =>\n\t\t\tstate.done === true\n\t\t\t\t? Option.none()\n\t\t\t\t: Option.some([state.value, iterator.next(state.value)] as const),\n\t\t)\n\t\treturn fromRules(domain, collected, options) as never\n\t}\n\nconst sameDomain = (policies: ReadonlyArray<Any>): Domain.Any => {\n\t// A copy of a policy made with a spread may keep the type fields of another\n\t// policy. Only a policy value goes into a tree.\n\tif (!Arr.every(policies, isPolicy)) {\n\t\tthrow new globalThis.Error(\"A value in the list is not a policy\")\n\t}\n\tconst domain = policies[0]!.domain\n\tif (!Arr.every(policies, (policy) => policy.domain === domain)) {\n\t\tthrow new globalThis.Error(\n\t\t\t\"Policies from two domains can not be put together\",\n\t\t)\n\t}\n\treturn domain\n}\n\n// The constructors of the nodes stop a tree past `policyDepth` or\n// `policyNodes`.\nexport const combine =\n\t(tag: \"All\" | \"Any\" | \"FirstOf\") =>\n\t(policies: ReadonlyArray<Any>): any =>\n\t\tfromNode(\n\t\t\tsameDomain(policies),\n\t\t\tnew PolicyNodes[tag](Arr.map(policies, nodeOf)),\n\t\t)\n\n/** Puts a limit on a policy. A deny from `cap` always wins. */\nexport const ceiling = dual(2, (policy: Any, cap: Any): any =>\n\tfromNode(\n\t\tsameDomain([policy, cap]),\n\t\tnew PolicyNodes.Ceiling({ cap: nodeOf(cap), policy: nodeOf(policy) }),\n\t),\n)\n\n/**\n * Gives the services to the predicates of a tree. A node with none stays. The\n * conditions get the services first, from a flat list of the rules, so the\n * walk does not go down the levels of the tree and of a condition at once.\n */\nconst provideNode = (\n\troot: PolicyNodes.Node,\n\tcontext: Context.Context<never>,\n): PolicyNodes.Node => {\n\t// A native map, because it must find a rule by the rule itself: two rules\n\t// with the same data are `Equal`.\n\tconst provided = new Map<RuleData.Rule, RuleData.Rule>(\n\t\tArr.map(PolicyNodes.rulesOf(root), (value) => [\n\t\t\tvalue,\n\t\t\tvalue.when === undefined || !Nodes.toNode(value.when).opaque\n\t\t\t\t? value\n\t\t\t\t: RuleData.make({\n\t\t\t\t\t\t...value,\n\t\t\t\t\t\twhen: Condition.provideContext(\n\t\t\t\t\t\t\tvalue.when as Condition.Any,\n\t\t\t\t\t\t\tcontext as never,\n\t\t\t\t\t\t),\n\t\t\t\t\t}),\n\t\t]),\n\t)\n\tconst provide = (node: PolicyNodes.Node): PolicyNodes.Node => {\n\t\tif (!node.opaque) {\n\t\t\treturn node\n\t\t}\n\t\tswitch (node._tag) {\n\t\t\tcase \"Rules\":\n\t\t\t\treturn new PolicyNodes.Rules({\n\t\t\t\t\talgorithm: node.algorithm,\n\t\t\t\t\trules: Arr.map(node.rules, (value) => provided.get(value) ?? value),\n\t\t\t\t})\n\t\t\tcase \"All\":\n\t\t\tcase \"Any\":\n\t\t\tcase \"FirstOf\":\n\t\t\t\treturn new PolicyNodes[node._tag](Arr.map(node.policies, provide))\n\t\t\tcase \"Ceiling\":\n\t\t\t\treturn new PolicyNodes.Ceiling({\n\t\t\t\t\tcap: provide(node.cap),\n\t\t\t\t\tpolicy: provide(node.policy),\n\t\t\t\t})\n\t\t}\n\t}\n\treturn provide(root)\n}\n\n/** Gives the services in `context` to every predicate of a policy. */\nexport const provideContext = dual(\n\t2,\n\t(policy: Any, context: Context.Context<never>): any =>\n\t\tfromNode(policy.domain, provideNode(nodeOf(policy), context)),\n)\n\n/** Tests if a value is a policy. */\nexport const isPolicy = (u: unknown): u is Any => PolicyData.is(u)\n\n/** The Schema of the policies of a domain with no predicates. */\nexport const schema = (domain: Domain.Any): Schema.Top =>\n\tSchema.Unknown.pipe(\n\t\tSchema.decodeTo(\n\t\t\t// A policy with a predicate is not a plain policy, and it can not\n\t\t\t// become JSON.\n\t\t\tSchema.declare(\n\t\t\t\t(u): u is Any =>\n\t\t\t\t\tisPolicy(u) && u.domain === domain && !nodeOf(u).opaque,\n\t\t\t).check(\n\t\t\t\tSchema.makeFilter((policy) => Bind.treeIssues(domain, nodeOf(policy))),\n\t\t\t),\n\t\t\tSchemaTransformation.transformEffect({\n\t\t\t\tdecode: (input: unknown, options) =>\n\t\t\t\t\tPolicyJson.decode(input, domain).pipe(\n\t\t\t\t\t\tEffect.map((node) => fromNode(domain, node)),\n\t\t\t\t\t\tEffect.mapError(\n\t\t\t\t\t\t\t(problem) =>\n\t\t\t\t\t\t\t\tnew SchemaIssue.Pointer(\n\t\t\t\t\t\t\t\t\tproblem.path,\n\t\t\t\t\t\t\t\t\tproblem.issue ??\n\t\t\t\t\t\t\t\t\t\tnew SchemaIssue.InvalidValue(\n\t\t\t\t\t\t\t\t\t\t\t{ message: problem.message },\n\t\t\t\t\t\t\t\t\t\t\tinput,\n\t\t\t\t\t\t\t\t\t\t\toptions,\n\t\t\t\t\t\t\t\t\t\t),\n\t\t\t\t\t\t\t\t),\n\t\t\t\t\t\t),\n\t\t\t\t\t),\n\t\t\t\tencode: (policy: Any) =>\n\t\t\t\t\tEffect.mapError(\n\t\t\t\t\t\tPolicyJson.encode(nodeOf(policy), domain.meta),\n\t\t\t\t\t\t(error) => error.issue,\n\t\t\t\t\t),\n\t\t\t}),\n\t\t),\n\t) as never\n"],"mappings":"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"}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import * as Arr from "effect/Array";
|
|
2
|
+
import * as Option from "effect/Option";
|
|
3
|
+
// src/internal/read.ts
|
|
4
|
+
/**
|
|
5
|
+
* How a condition reads a field of a value. A condition reads only plain
|
|
6
|
+
* data: an own data field that a loop over the keys sees. This is the data
|
|
7
|
+
* that a Schema describes, and that every value from Effect has, so the
|
|
8
|
+
* catalog of the Schema holds every value that a read can give.
|
|
9
|
+
*/
|
|
10
|
+
/** The prototypes of a value, from its own prototype up. */
|
|
11
|
+
const prototypesOf = (value) => Arr.unfold(Object.getPrototypeOf(value), (prototype) => prototype === null ? Option.none() : Option.some([prototype, Object.getPrototypeOf(prototype)]));
|
|
12
|
+
/**
|
|
13
|
+
* Reads one part of a value:
|
|
14
|
+
* - an own data field that a loop over the keys sees gives its value, with
|
|
15
|
+
* `null` for `undefined`;
|
|
16
|
+
* - a part of `null`, of a scalar, or a missing field gives `null`;
|
|
17
|
+
* - a name that the value has only from `Object.prototype`, such as
|
|
18
|
+
* `constructor`, gives `null`.
|
|
19
|
+
*
|
|
20
|
+
* Any other field, such as a getter, a hidden field, or a field from the
|
|
21
|
+
* prototype of a class, is a defect. Its value is not data that the Schema
|
|
22
|
+
* describes, and a read of it as `null` could let a value pass a deny rule.
|
|
23
|
+
*/
|
|
24
|
+
const readPart = (value, name) => {
|
|
25
|
+
if (typeof value !== "object" || value === null) return null;
|
|
26
|
+
const property = Object.getOwnPropertyDescriptor(value, name);
|
|
27
|
+
if (property === void 0) {
|
|
28
|
+
const owner = Arr.findFirst(prototypesOf(value), (prototype) => Object.hasOwn(prototype, name));
|
|
29
|
+
if (Option.isNone(owner) || owner.value === Object.prototype) return null;
|
|
30
|
+
throw new Error(`The field "${name}" of the value is not plain data`);
|
|
31
|
+
}
|
|
32
|
+
if (!("value" in property) || property.enumerable !== true) throw new Error(`The field "${name}" of the value is not plain data`);
|
|
33
|
+
return property.value === void 0 ? null : property.value;
|
|
34
|
+
};
|
|
35
|
+
/** Reads a field of a value by the names of its parts. */
|
|
36
|
+
const readPath = (value, names) => Arr.reduce(names, value, (current, name) => readPart(current, name));
|
|
37
|
+
export { readPart, readPath };
|
|
38
|
+
|
|
39
|
+
//# sourceMappingURL=read.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"read.js","names":[],"sources":["../../src/internal/read.ts"],"sourcesContent":["/**\n * How a condition reads a field of a value. A condition reads only plain\n * data: an own data field that a loop over the keys sees. This is the data\n * that a Schema describes, and that every value from Effect has, so the\n * catalog of the Schema holds every value that a read can give.\n */\nimport * as Arr from \"effect/Array\"\nimport * as Option from \"effect/Option\"\n\n/** The prototypes of a value, from its own prototype up. */\nconst prototypesOf = (value: object): ReadonlyArray<object> =>\n\tArr.unfold(Object.getPrototypeOf(value) as object | null, (prototype) =>\n\t\tprototype === null\n\t\t\t? Option.none()\n\t\t\t: Option.some([prototype, Object.getPrototypeOf(prototype)] as const),\n\t)\n\n/**\n * Reads one part of a value:\n * - an own data field that a loop over the keys sees gives its value, with\n * `null` for `undefined`;\n * - a part of `null`, of a scalar, or a missing field gives `null`;\n * - a name that the value has only from `Object.prototype`, such as\n * `constructor`, gives `null`.\n *\n * Any other field, such as a getter, a hidden field, or a field from the\n * prototype of a class, is a defect. Its value is not data that the Schema\n * describes, and a read of it as `null` could let a value pass a deny rule.\n */\nexport const readPart = (value: unknown, name: string): unknown => {\n\tif (typeof value !== \"object\" || value === null) {\n\t\treturn null\n\t}\n\tconst property = Object.getOwnPropertyDescriptor(value, name)\n\tif (property === undefined) {\n\t\tconst owner = Arr.findFirst(prototypesOf(value), (prototype) =>\n\t\t\tObject.hasOwn(prototype, name),\n\t\t)\n\t\tif (Option.isNone(owner) || owner.value === Object.prototype) {\n\t\t\treturn null\n\t\t}\n\t\tthrow new Error(`The field \"${name}\" of the value is not plain data`)\n\t}\n\tif (!(\"value\" in property) || property.enumerable !== true) {\n\t\tthrow new Error(`The field \"${name}\" of the value is not plain data`)\n\t}\n\treturn property.value === undefined ? null : property.value\n}\n\n/** Reads a field of a value by the names of its parts. */\nexport const readPath = (\n\tvalue: unknown,\n\tnames: ReadonlyArray<string>,\n): unknown =>\n\tArr.reduce(names, value, (current, name) => readPart(current, name))\n"],"mappings":";;;;;;;;;;AAUA,MAAM,gBAAgB,UACrB,IAAI,OAAO,OAAO,eAAe,KAAK,IAAqB,cAC1D,cAAc,OACX,OAAO,KAAK,IACZ,OAAO,KAAK,CAAC,WAAW,OAAO,eAAe,SAAS,CAAC,CAAU,CACtE;;;;;;;;;;;;;AAcD,MAAa,YAAY,OAAgB,SAA0B;CAClE,IAAI,OAAO,UAAU,YAAY,UAAU,MAC1C,OAAO;CAER,MAAM,WAAW,OAAO,yBAAyB,OAAO,IAAI;CAC5D,IAAI,aAAa,KAAA,GAAW;EAC3B,MAAM,QAAQ,IAAI,UAAU,aAAa,KAAK,IAAI,cACjD,OAAO,OAAO,WAAW,IAAI,CAC9B;EACA,IAAI,OAAO,OAAO,KAAK,KAAK,MAAM,UAAU,OAAO,WAClD,OAAO;EAER,MAAM,IAAI,MAAM,cAAc,KAAK,iCAAiC;CACrE;CACA,IAAI,EAAE,WAAW,aAAa,SAAS,eAAe,MACrD,MAAM,IAAI,MAAM,cAAc,KAAK,iCAAiC;CAErE,OAAO,SAAS,UAAU,KAAA,IAAY,OAAO,SAAS;AACvD;;AAGA,MAAa,YACZ,OACA,UAEA,IAAI,OAAO,OAAO,QAAQ,SAAS,SAAS,SAAS,SAAS,IAAI,CAAC"}
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import { catalogOf } from "./catalog.js";
|
|
2
|
+
import * as Schema from "effect/Schema";
|
|
3
|
+
import { Class } from "effect/Data";
|
|
4
|
+
import * as Arr from "effect/Array";
|
|
5
|
+
import { hasProperty } from "effect/Predicate";
|
|
6
|
+
// src/internal/resource.ts
|
|
7
|
+
/**
|
|
8
|
+
* The values of resources: their descriptors, and the constructors of
|
|
9
|
+
* `Resource.Class` and `Resource.make`. The types are in `Resource.ts`.
|
|
10
|
+
*/
|
|
11
|
+
/**
|
|
12
|
+
* The class of the runtime data of a resource. `isResource` uses it to
|
|
13
|
+
* know a resource.
|
|
14
|
+
*/
|
|
15
|
+
var ResourceDescriptor = class extends Class {};
|
|
16
|
+
/**
|
|
17
|
+
* Checks the names of the top fields of a resource at once, when it is made.
|
|
18
|
+
* The names of nested fields are checked when the descriptor is made.
|
|
19
|
+
*/
|
|
20
|
+
const checkFields = (kind, options) => {
|
|
21
|
+
if (Object.hasOwn(options.fields, "_tag")) throw new Error(`The resource "${kind}" may not have a field "_tag"`);
|
|
22
|
+
Arr.map(Object.keys(options.fields), (name) => {
|
|
23
|
+
if (name.includes(".")) throw new Error(`A field name may not have a dot: "${name}"`);
|
|
24
|
+
if (name === "") throw new Error("A field name may not be empty");
|
|
25
|
+
});
|
|
26
|
+
};
|
|
27
|
+
/**
|
|
28
|
+
* Gives a value that is made on first use, and then kept. The descriptor of a
|
|
29
|
+
* resource reads its Schema, and a recursive Schema refers to the resource
|
|
30
|
+
* class, so it can not be read while the class is made. The cell belongs to
|
|
31
|
+
* one resource.
|
|
32
|
+
*/
|
|
33
|
+
const lazy = (make) => {
|
|
34
|
+
const cell = {};
|
|
35
|
+
return () => {
|
|
36
|
+
if (!("value" in cell)) cell.value = make();
|
|
37
|
+
return cell.value;
|
|
38
|
+
};
|
|
39
|
+
};
|
|
40
|
+
/**
|
|
41
|
+
* Checks each part of the path of a resource: a string, or a reference to a
|
|
42
|
+
* top-level field of the resource. A reference to another field would read
|
|
43
|
+
* nothing, and a rule on the path could then match a value by mistake.
|
|
44
|
+
*/
|
|
45
|
+
const checkPath = (kind, fields, path) => Arr.map(Array.from(path), (segment) => {
|
|
46
|
+
if (typeof segment === "string") return segment;
|
|
47
|
+
if (hasProperty(segment, "_tag") && segment._tag === "Field" && hasProperty(segment, "field") && typeof segment.field === "string" && Object.hasOwn(fields, segment.field)) return {
|
|
48
|
+
_tag: "Field",
|
|
49
|
+
field: segment.field
|
|
50
|
+
};
|
|
51
|
+
throw new globalThis.Error(`A part of the path of "${kind}" is not a string or a field of the resource`);
|
|
52
|
+
});
|
|
53
|
+
/**
|
|
54
|
+
* Copies the options of a resource. The descriptor is made later, when it is
|
|
55
|
+
* first read, so a change to the objects of the caller before then must not
|
|
56
|
+
* change it: the Schema of the values and the descriptor must agree.
|
|
57
|
+
*/
|
|
58
|
+
const snapshot = (options) => ({
|
|
59
|
+
...options,
|
|
60
|
+
actions: [...options.actions],
|
|
61
|
+
fields: { ...options.fields }
|
|
62
|
+
});
|
|
63
|
+
const makeDescriptor = (kind, options) => {
|
|
64
|
+
const refs = Object.fromEntries(Arr.map(Object.keys(options.fields), (key) => [key, {
|
|
65
|
+
_tag: "Field",
|
|
66
|
+
field: key
|
|
67
|
+
}]));
|
|
68
|
+
const struct = Schema.Struct(options.fields);
|
|
69
|
+
return new ResourceDescriptor({
|
|
70
|
+
kind,
|
|
71
|
+
actions: [...options.actions],
|
|
72
|
+
path: options.path === void 0 ? void 0 : checkPath(kind, options.fields, options.path(refs)),
|
|
73
|
+
catalog: catalogOf(struct.ast),
|
|
74
|
+
schema: Schema.TaggedStruct(kind, options.fields)
|
|
75
|
+
});
|
|
76
|
+
};
|
|
77
|
+
/** Makes the class of `Resource.Class`. */
|
|
78
|
+
const makeClass = (identifier) => (kind, input) => {
|
|
79
|
+
const options = snapshot(input);
|
|
80
|
+
checkFields(kind, options);
|
|
81
|
+
const Base = Schema.TaggedClass(identifier)(kind, options.fields);
|
|
82
|
+
const descriptor = lazy(() => makeDescriptor(kind, options));
|
|
83
|
+
return class extends Base {
|
|
84
|
+
static get descriptor() {
|
|
85
|
+
return descriptor();
|
|
86
|
+
}
|
|
87
|
+
static kind = kind;
|
|
88
|
+
static actions = [...options.actions];
|
|
89
|
+
};
|
|
90
|
+
};
|
|
91
|
+
/** Makes a resource of `Resource.make`. */
|
|
92
|
+
const makeStruct = (kind, input) => {
|
|
93
|
+
const options = snapshot(input);
|
|
94
|
+
checkFields(kind, options);
|
|
95
|
+
const schema = Schema.TaggedStruct(kind, options.fields);
|
|
96
|
+
const descriptor = lazy(() => makeDescriptor(kind, options));
|
|
97
|
+
return {
|
|
98
|
+
get descriptor() {
|
|
99
|
+
return descriptor();
|
|
100
|
+
},
|
|
101
|
+
kind,
|
|
102
|
+
actions: [...options.actions],
|
|
103
|
+
schema,
|
|
104
|
+
make: (input) => schema.make(input)
|
|
105
|
+
};
|
|
106
|
+
};
|
|
107
|
+
/** Tests if a value is a resource. */
|
|
108
|
+
const isResource = (u) => hasProperty(u, "descriptor") && u.descriptor instanceof ResourceDescriptor;
|
|
109
|
+
/** Gets the runtime data of a resource. */
|
|
110
|
+
const descriptor = (resource) => resource.descriptor;
|
|
111
|
+
/** Gives the kind name of a resource value, from its `_tag` field. */
|
|
112
|
+
const kindOf = (value) => hasProperty(value, "_tag") && typeof value._tag === "string" ? value._tag : void 0;
|
|
113
|
+
export { descriptor, isResource, kindOf, makeClass, makeStruct };
|
|
114
|
+
|
|
115
|
+
//# sourceMappingURL=resource.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"resource.js","names":["DataClass","Catalog.catalogOf"],"sources":["../../src/internal/resource.ts"],"sourcesContent":["/**\n * The values of resources: their descriptors, and the constructors of\n * `Resource.Class` and `Resource.make`. The types are in `Resource.ts`.\n */\nimport * as Arr from \"effect/Array\"\nimport { Class as DataClass } from \"effect/Data\"\nimport { hasProperty } from \"effect/Predicate\"\nimport * as Schema from \"effect/Schema\"\nimport type {\n\tAny,\n\tDescriptor,\n\tFieldRef,\n\tOptions,\n\tPathSegment,\n} from \"../Resource.ts\"\nimport * as Catalog from \"./catalog.ts\"\n\n/**\n * The class of the runtime data of a resource. `isResource` uses it to\n * know a resource.\n */\nclass ResourceDescriptor extends DataClass<Descriptor> {}\n\n/**\n * Checks the names of the top fields of a resource at once, when it is made.\n * The names of nested fields are checked when the descriptor is made.\n */\nconst checkFields = (\n\tkind: string,\n\toptions: Options<ReadonlyArray<string>, Schema.Struct.Fields>,\n): void => {\n\t// The kind of a value comes from its `_tag`. A field with this name could\n\t// give a value another kind, so it is a defect.\n\tif (Object.hasOwn(options.fields, \"_tag\")) {\n\t\tthrow new Error(`The resource \"${kind}\" may not have a field \"_tag\"`)\n\t}\n\tArr.map(Object.keys(options.fields), (name) => {\n\t\tif (name.includes(\".\")) {\n\t\t\tthrow new Error(`A field name may not have a dot: \"${name}\"`)\n\t\t}\n\t\tif (name === \"\") {\n\t\t\tthrow new Error(\"A field name may not be empty\")\n\t\t}\n\t})\n}\n\n/**\n * Gives a value that is made on first use, and then kept. The descriptor of a\n * resource reads its Schema, and a recursive Schema refers to the resource\n * class, so it can not be read while the class is made. The cell belongs to\n * one resource.\n */\nconst lazy = <A>(make: () => A): (() => A) => {\n\tconst cell: { value?: A } = {}\n\treturn () => {\n\t\tif (!(\"value\" in cell)) {\n\t\t\tcell.value = make()\n\t\t}\n\t\treturn cell.value as A\n\t}\n}\n\n/**\n * Checks each part of the path of a resource: a string, or a reference to a\n * top-level field of the resource. A reference to another field would read\n * nothing, and a rule on the path could then match a value by mistake.\n */\nconst checkPath = (\n\tkind: string,\n\tfields: Schema.Struct.Fields,\n\tpath: ReadonlyArray<unknown>,\n): ReadonlyArray<PathSegment> =>\n\t// `Array.from` gives `undefined` for a hole of a sparse array, so the check\n\t// sees each place.\n\tArr.map(Array.from(path), (segment): PathSegment => {\n\t\tif (typeof segment === \"string\") {\n\t\t\treturn segment\n\t\t}\n\t\tif (\n\t\t\thasProperty(segment, \"_tag\") &&\n\t\t\tsegment._tag === \"Field\" &&\n\t\t\thasProperty(segment, \"field\") &&\n\t\t\ttypeof segment.field === \"string\" &&\n\t\t\tObject.hasOwn(fields, segment.field)\n\t\t) {\n\t\t\treturn { _tag: \"Field\", field: segment.field }\n\t\t}\n\t\tthrow new globalThis.Error(\n\t\t\t`A part of the path of \"${kind}\" is not a string or a field of the resource`,\n\t\t)\n\t})\n\n/**\n * Copies the options of a resource. The descriptor is made later, when it is\n * first read, so a change to the objects of the caller before then must not\n * change it: the Schema of the values and the descriptor must agree.\n */\nconst snapshot = (\n\toptions: Options<ReadonlyArray<string>, Schema.Struct.Fields>,\n): Options<ReadonlyArray<string>, Schema.Struct.Fields> => ({\n\t...options,\n\tactions: [...options.actions],\n\tfields: { ...options.fields },\n})\n\nconst makeDescriptor = (\n\tkind: string,\n\toptions: Options<ReadonlyArray<string>, Schema.Struct.Fields>,\n): Descriptor => {\n\tconst refs: Record<string, FieldRef<string>> = Object.fromEntries(\n\t\tArr.map(Object.keys(options.fields), (key) => [\n\t\t\tkey,\n\t\t\t{ _tag: \"Field\", field: key },\n\t\t]),\n\t)\n\tconst struct = Schema.Struct(options.fields)\n\treturn new ResourceDescriptor({\n\t\tkind,\n\t\tactions: [...options.actions],\n\t\tpath:\n\t\t\toptions.path === undefined\n\t\t\t\t? undefined\n\t\t\t\t: checkPath(kind, options.fields, options.path(refs)),\n\t\tcatalog: Catalog.catalogOf(struct.ast),\n\t\tschema: Schema.TaggedStruct(kind, options.fields),\n\t})\n}\n\n/** Makes the class of `Resource.Class`. */\nexport const makeClass =\n\t(identifier?: string) =>\n\t(\n\t\tkind: string,\n\t\tinput: Options<ReadonlyArray<string>, Schema.Struct.Fields>,\n\t) => {\n\t\tconst options = snapshot(input)\n\t\tcheckFields(kind, options)\n\t\tconst Base = Schema.TaggedClass<any>(identifier)(kind, options.fields)\n\t\tconst descriptor = lazy(() => makeDescriptor(kind, options))\n\t\treturn class extends Base {\n\t\t\tstatic get descriptor() {\n\t\t\t\treturn descriptor()\n\t\t\t}\n\t\t\tstatic readonly kind = kind\n\t\t\tstatic readonly actions = [...options.actions]\n\t\t}\n\t}\n\n/** Makes a resource of `Resource.make`. */\nexport const makeStruct = (\n\tkind: string,\n\tinput: Options<ReadonlyArray<string>, Schema.Struct.Fields>,\n) => {\n\tconst options = snapshot(input)\n\tcheckFields(kind, options)\n\tconst schema = Schema.TaggedStruct(kind, options.fields)\n\tconst descriptor = lazy(() => makeDescriptor(kind, options))\n\treturn {\n\t\tget descriptor() {\n\t\t\treturn descriptor()\n\t\t},\n\t\tkind,\n\t\tactions: [...options.actions],\n\t\tschema,\n\t\tmake: (input: object) => schema.make(input as never),\n\t}\n}\n\n/** Tests if a value is a resource. */\nexport const isResource = (u: unknown): u is Any =>\n\thasProperty(u, \"descriptor\") && u.descriptor instanceof ResourceDescriptor\n\n/** Gets the runtime data of a resource. */\nexport const descriptor = (resource: Any): Descriptor => resource.descriptor\n\n/** Gives the kind name of a resource value, from its `_tag` field. */\nexport const kindOf = (value: unknown): string | undefined =>\n\thasProperty(value, \"_tag\") && typeof value._tag === \"string\"\n\t\t? value._tag\n\t\t: undefined\n"],"mappings":"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"}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import "../Resource.js";
|
|
2
|
+
import { Ast } from "../Condition.js";
|
|
3
|
+
import { Any } from "../Domain.js";
|
|
4
|
+
import * as Data from "effect/Data";
|
|
5
|
+
/** The data of a rule. */
|
|
6
|
+
export interface Shape {
|
|
7
|
+
readonly _tag: "Allow" | "Deny";
|
|
8
|
+
readonly actions: ReadonlyArray<string>;
|
|
9
|
+
readonly resources: ReadonlyArray<string>;
|
|
10
|
+
readonly when?: Ast;
|
|
11
|
+
readonly fields?: ReadonlyArray<string>;
|
|
12
|
+
readonly reason?: string;
|
|
13
|
+
readonly id?: string;
|
|
14
|
+
readonly meta?: unknown;
|
|
15
|
+
/**
|
|
16
|
+
* The domain of the builder that made the rule. Two domains may have the
|
|
17
|
+
* same types but other paths, so the types can not tell them apart. It is
|
|
18
|
+
* not in the JSON form of a rule.
|
|
19
|
+
*/
|
|
20
|
+
readonly domain?: Any;
|
|
21
|
+
}
|
|
22
|
+
/** The class of every rule value. */
|
|
23
|
+
export declare class Rule extends Data.Class<Shape> {
|
|
24
|
+
[Symbol.iterator](): Generator<this, this, unknown>;
|
|
25
|
+
}
|
|
26
|
+
//# sourceMappingURL=rule.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"rule.d.ts","names":["Condition.Ast","Domain.Any"],"sources":["../../src/internal/rule.ts"],"mappings":";;;;;iBAiBiB;WACP;WACA,SAAS;WACT,WAAW;WACX,OAAOA;WACP,SAAS;WACT;WACA;WACA;;;;;;WAMA,SAASC;;;qBAIN,aAAa,KAAK,MAAM;GAClC,OAAO,aAAa"}
|