@nmnmcc/ability 2.0.1 → 2.0.4
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 +25 -17
- package/dist/Condition.d.ts +26 -16
- package/dist/Condition.d.ts.map +1 -1
- package/dist/Condition.js +2 -1
- package/dist/Condition.js.map +1 -1
- package/dist/Limits.d.ts +3 -1
- package/dist/Limits.d.ts.map +1 -1
- package/dist/Limits.js +3 -1
- package/dist/Limits.js.map +1 -1
- package/dist/Policy.d.ts +6 -8
- package/dist/Policy.d.ts.map +1 -1
- package/dist/Policy.js +4 -2
- package/dist/Policy.js.map +1 -1
- package/dist/internal/algebra.js +23 -13
- package/dist/internal/algebra.js.map +1 -1
- package/dist/internal/analysis.js +4 -2
- package/dist/internal/analysis.js.map +1 -1
- package/dist/internal/authorizer.js +13 -7
- package/dist/internal/authorizer.js.map +1 -1
- package/dist/internal/bind.js +53 -27
- package/dist/internal/bind.js.map +1 -1
- package/dist/internal/catalog.d.ts +9 -0
- package/dist/internal/catalog.d.ts.map +1 -1
- package/dist/internal/catalog.js +37 -22
- package/dist/internal/catalog.js.map +1 -1
- package/dist/internal/decide.js +2 -1
- package/dist/internal/decide.js.map +1 -1
- package/dist/internal/place.js +19 -0
- package/dist/internal/place.js.map +1 -0
- package/dist/internal/policy-json.js +39 -39
- package/dist/internal/policy-json.js.map +1 -1
- package/dist/internal/rule.d.ts +20 -22
- package/dist/internal/rule.d.ts.map +1 -1
- package/dist/internal/rule.js +32 -4
- package/dist/internal/rule.js.map +1 -1
- package/package.json +1 -1
- package/src/Condition.ts +64 -50
- package/src/Limits.ts +3 -1
- package/src/Policy.ts +5 -9
- package/src/internal/algebra.ts +30 -13
- package/src/internal/analysis.ts +5 -0
- package/src/internal/authorizer.ts +17 -7
- package/src/internal/bind.ts +85 -23
- package/src/internal/catalog.ts +78 -30
- package/src/internal/decide.ts +3 -0
- package/src/internal/place.ts +32 -0
- package/src/internal/policy-json.ts +49 -29
- package/src/internal/rule.ts +55 -11
package/src/internal/rule.ts
CHANGED
|
@@ -7,8 +7,11 @@
|
|
|
7
7
|
*/
|
|
8
8
|
import * as Arr from "effect/Array"
|
|
9
9
|
import * as Data from "effect/Data"
|
|
10
|
+
import * as MutableHashMap from "effect/MutableHashMap"
|
|
11
|
+
import * as Option from "effect/Option"
|
|
10
12
|
import * as Condition from "../Condition.ts"
|
|
11
13
|
import type * as Domain from "../Domain.ts"
|
|
14
|
+
import type * as Policy from "../Policy.ts"
|
|
12
15
|
import type * as Resource from "../Resource.ts"
|
|
13
16
|
import * as Catalog from "./catalog.ts"
|
|
14
17
|
import * as Nodes from "./nodes.ts"
|
|
@@ -33,17 +36,42 @@ export interface Shape {
|
|
|
33
36
|
readonly domain?: Domain.Any
|
|
34
37
|
}
|
|
35
38
|
|
|
36
|
-
/**
|
|
37
|
-
|
|
38
|
-
|
|
39
|
+
/**
|
|
40
|
+
* The class of every rule value. The types are those of `Policy.Rule`: `D` is
|
|
41
|
+
* the domain, `I` is what the tests of the condition read, and `X` is the
|
|
42
|
+
* mark of the condition. With no types, as in the nodes of a policy, a rule
|
|
43
|
+
* fits no domain, its condition fits no other rule, and it may have a
|
|
44
|
+
* predicate.
|
|
45
|
+
*/
|
|
46
|
+
export class Rule<D = never, I = never, X = unknown> extends Data.Class<
|
|
47
|
+
Policy.RuleShape<I, X>
|
|
48
|
+
> {
|
|
49
|
+
/** Only in the types. */
|
|
50
|
+
declare readonly "~Opaque": () => X
|
|
51
|
+
/** Only in the types. */
|
|
52
|
+
declare readonly "~Domain": (domain: D) => D
|
|
53
|
+
// A private field makes the type of a rule its own, as for a policy: a
|
|
54
|
+
// spread of a rule, or an object made by hand, is not a rule. `Equal` does
|
|
55
|
+
// not read it. It comes last, so its `;` stays: the generator method below
|
|
56
|
+
// needs one after a field. Node.js takes the `declare` fields out of the
|
|
57
|
+
// code, and a `;` after one of them would go with it.
|
|
58
|
+
readonly #rule = true;
|
|
59
|
+
|
|
60
|
+
*[Symbol.iterator](): Generator<Rule<D, I, X>, Rule<D, I, X>, unknown> {
|
|
39
61
|
yield this
|
|
40
62
|
return this
|
|
41
63
|
}
|
|
64
|
+
|
|
65
|
+
/** Tests if a value is a rule, by its private field. */
|
|
66
|
+
static is(u: unknown): u is Rule {
|
|
67
|
+
return typeof u === "object" && u !== null && #rule in u
|
|
68
|
+
}
|
|
42
69
|
}
|
|
43
70
|
|
|
44
71
|
/** Makes a rule from its data. It does not check the data against a domain. */
|
|
45
72
|
export const make = (data: Shape): Rule =>
|
|
46
73
|
// A missing option is left out, so two rules with the same data are equal.
|
|
74
|
+
// The data has no types here, as the code below works with no types.
|
|
47
75
|
new Rule({
|
|
48
76
|
_tag: data._tag,
|
|
49
77
|
actions: [...data.actions],
|
|
@@ -54,7 +82,7 @@ export const make = (data: Shape): Rule =>
|
|
|
54
82
|
...(data.id === undefined ? {} : { id: data.id }),
|
|
55
83
|
...(data.meta === undefined ? {} : { meta: data.meta }),
|
|
56
84
|
...(data.domain === undefined ? {} : { domain: data.domain }),
|
|
57
|
-
})
|
|
85
|
+
} as never)
|
|
58
86
|
|
|
59
87
|
/**
|
|
60
88
|
* Makes a rule from its data, as `Policy.rule` does. A condition with a
|
|
@@ -71,7 +99,7 @@ export const makePlain = (data: Shape): Rule => {
|
|
|
71
99
|
}
|
|
72
100
|
|
|
73
101
|
/** Tests if a value is a rule. */
|
|
74
|
-
export const isRule = (u: unknown): u is Rule => u
|
|
102
|
+
export const isRule = (u: unknown): u is Rule => Rule.is(u)
|
|
75
103
|
|
|
76
104
|
const toArray = <A>(value: A | ReadonlyArray<A>): ReadonlyArray<A> =>
|
|
77
105
|
Array.isArray(value) ? value : [value as A]
|
|
@@ -132,6 +160,25 @@ export const matchesField = (pattern: string, field: string): boolean =>
|
|
|
132
160
|
export const matchesInside = (pattern: string, field: string): boolean =>
|
|
133
161
|
Patterns.matchesBelow(Patterns.parseBound(pattern), field.split("."))
|
|
134
162
|
|
|
163
|
+
/**
|
|
164
|
+
* Makes the test "a field pattern matches some field of the catalog" for one
|
|
165
|
+
* walk, such as one decision. A policy may name one pattern very many times,
|
|
166
|
+
* so the test keeps each answer by the text of the pattern, and the memo goes
|
|
167
|
+
* away when the walk ends. The binding puts a limit on the work of the
|
|
168
|
+
* patterns of a policy on each kind, so one walk does at most that work.
|
|
169
|
+
*/
|
|
170
|
+
export const fitsOf = (
|
|
171
|
+
catalog: Catalog.Catalog,
|
|
172
|
+
): ((pattern: string) => boolean) => {
|
|
173
|
+
const memo = MutableHashMap.empty<string, boolean>()
|
|
174
|
+
return (pattern) =>
|
|
175
|
+
Option.getOrElse(MutableHashMap.get(memo, pattern), () => {
|
|
176
|
+
const found = Patterns.fitsCatalog(Patterns.parseBound(pattern), catalog)
|
|
177
|
+
MutableHashMap.set(memo, pattern, found)
|
|
178
|
+
return found
|
|
179
|
+
})
|
|
180
|
+
}
|
|
181
|
+
|
|
135
182
|
/**
|
|
136
183
|
* Tests the parts of a rule that do not need the resource value: the action,
|
|
137
184
|
* the kind and the field.
|
|
@@ -152,6 +199,8 @@ export const matchesTarget = (
|
|
|
152
199
|
readonly kind: string
|
|
153
200
|
readonly field?: string | undefined
|
|
154
201
|
readonly descriptor?: Resource.Descriptor | undefined
|
|
202
|
+
/** The test of {@link fitsOf} for the catalog of the descriptor. */
|
|
203
|
+
readonly fits?: ((pattern: string) => boolean) | undefined
|
|
155
204
|
},
|
|
156
205
|
): boolean => {
|
|
157
206
|
if (!rule.actions.includes("*") && !rule.actions.includes(target.action)) {
|
|
@@ -173,12 +222,7 @@ export const matchesTarget = (
|
|
|
173
222
|
return (
|
|
174
223
|
rule._tag === "Allow" &&
|
|
175
224
|
(descriptor === undefined ||
|
|
176
|
-
Arr.some(rule.fields, (
|
|
177
|
-
Patterns.fitsCatalog(
|
|
178
|
-
Patterns.parseBound(pattern),
|
|
179
|
-
descriptor.catalog,
|
|
180
|
-
),
|
|
181
|
-
))
|
|
225
|
+
Arr.some(rule.fields, target.fits ?? fitsOf(descriptor.catalog)))
|
|
182
226
|
)
|
|
183
227
|
}
|
|
184
228
|
const field = target.field
|