@nmnmcc/ability 2.0.4 → 2.0.6
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 +7 -7
- package/dist/Condition.d.ts +4 -4
- package/dist/Condition.d.ts.map +1 -1
- package/dist/Condition.js.map +1 -1
- package/dist/Domain.d.ts +1 -1
- package/dist/Policy.d.ts +2 -2
- package/dist/Policy.d.ts.map +1 -1
- package/dist/Policy.js.map +1 -1
- package/dist/internal/algebra.js +85 -10
- package/dist/internal/algebra.js.map +1 -1
- package/dist/internal/authorizer.d.ts.map +1 -1
- package/dist/internal/authorizer.js +37 -14
- package/dist/internal/authorizer.js.map +1 -1
- package/dist/internal/bind.js +15 -12
- package/dist/internal/bind.js.map +1 -1
- package/dist/internal/catalog.d.ts +6 -0
- package/dist/internal/catalog.d.ts.map +1 -1
- package/dist/internal/catalog.js +12 -9
- package/dist/internal/catalog.js.map +1 -1
- package/dist/internal/decide.js +1 -1
- package/dist/internal/domain.js +1 -1
- package/dist/internal/json.js +2 -10
- package/dist/internal/json.js.map +1 -1
- package/dist/internal/nodes.d.ts +4 -1
- package/dist/internal/nodes.d.ts.map +1 -1
- package/dist/internal/nodes.js +17 -7
- package/dist/internal/nodes.js.map +1 -1
- package/dist/internal/patterns.js +1 -1
- package/dist/internal/patterns.js.map +1 -1
- package/dist/internal/policy-json.js +2 -8
- package/dist/internal/policy-json.js.map +1 -1
- package/dist/internal/policy-nodes.d.ts +4 -1
- package/dist/internal/policy-nodes.d.ts.map +1 -1
- package/dist/internal/policy-nodes.js +13 -4
- package/dist/internal/policy-nodes.js.map +1 -1
- package/dist/internal/rule.d.ts.map +1 -1
- package/dist/internal/rule.js +6 -6
- package/dist/internal/rule.js.map +1 -1
- package/dist/internal/search.d.ts.map +1 -1
- package/dist/internal/search.js +51 -34
- package/dist/internal/search.js.map +1 -1
- package/package.json +1 -1
- package/src/Condition.ts +5 -2
- package/src/Policy.ts +1 -2
- package/src/internal/algebra.ts +118 -21
- package/src/internal/authorizer.ts +56 -14
- package/src/internal/bind.ts +39 -18
- package/src/internal/catalog.ts +40 -27
- package/src/internal/json.ts +2 -11
- package/src/internal/nodes.ts +21 -11
- package/src/internal/patterns.ts +7 -3
- package/src/internal/policy-json.ts +2 -9
- package/src/internal/policy-nodes.ts +14 -6
- package/src/internal/rule.ts +13 -9
- package/src/internal/search.ts +81 -75
package/src/internal/bind.ts
CHANGED
|
@@ -7,7 +7,6 @@
|
|
|
7
7
|
*/
|
|
8
8
|
import * as Arr from "effect/Array"
|
|
9
9
|
import * as HashMap from "effect/HashMap"
|
|
10
|
-
import * as MutableHashMap from "effect/MutableHashMap"
|
|
11
10
|
import * as Option from "effect/Option"
|
|
12
11
|
import * as Result from "effect/Result"
|
|
13
12
|
import type * as Domain from "../Domain.ts"
|
|
@@ -39,10 +38,18 @@ export interface RuleData {
|
|
|
39
38
|
* time. It goes away when the binding ends.
|
|
40
39
|
*/
|
|
41
40
|
interface Memo {
|
|
42
|
-
/**
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
41
|
+
/**
|
|
42
|
+
* The suggestion for each name, by the place where it was looked for. The
|
|
43
|
+
* names come from a policy, so the map is native, as `fits` is.
|
|
44
|
+
*/
|
|
45
|
+
readonly suggestions: Map<string, string>
|
|
46
|
+
/**
|
|
47
|
+
* If a pattern matches a field of a kind, by the kind and the pattern. The
|
|
48
|
+
* patterns come from a policy, so the map is native: the hash of a string
|
|
49
|
+
* in Effect has no random seed, and a policy could hold very many
|
|
50
|
+
* patterns with one hash. The engine hashes a string with a random seed.
|
|
51
|
+
*/
|
|
52
|
+
readonly fits: Map<string, boolean>
|
|
46
53
|
/** The count of the issues of conditions so far. It belongs to one binding. */
|
|
47
54
|
readonly issues: { count: number; noted: boolean }
|
|
48
55
|
/** The steps of the checks of patterns so far. It belongs to one binding. */
|
|
@@ -50,8 +57,8 @@ interface Memo {
|
|
|
50
57
|
}
|
|
51
58
|
|
|
52
59
|
const makeMemo = (): Memo => ({
|
|
53
|
-
suggestions:
|
|
54
|
-
fits:
|
|
60
|
+
suggestions: new Map(),
|
|
61
|
+
fits: new Map(),
|
|
55
62
|
issues: { count: 0, noted: false },
|
|
56
63
|
work: { steps: 0, noted: false },
|
|
57
64
|
})
|
|
@@ -72,15 +79,15 @@ const suggestIn = (
|
|
|
72
79
|
names: () => ReadonlyArray<string>,
|
|
73
80
|
): string => {
|
|
74
81
|
const key = `${scope}\u0000${name}`
|
|
75
|
-
const known =
|
|
76
|
-
if (
|
|
77
|
-
return known
|
|
82
|
+
const known = memo.suggestions.get(key)
|
|
83
|
+
if (known !== undefined) {
|
|
84
|
+
return known
|
|
78
85
|
}
|
|
79
|
-
if (
|
|
86
|
+
if (memo.suggestions.size >= maxSuggestions) {
|
|
80
87
|
return message
|
|
81
88
|
}
|
|
82
89
|
const made = withSuggestion(message, name, names())
|
|
83
|
-
|
|
90
|
+
memo.suggestions.set(key, made)
|
|
84
91
|
return made
|
|
85
92
|
}
|
|
86
93
|
|
|
@@ -230,7 +237,7 @@ const patternIssues = (
|
|
|
230
237
|
Arr.reduce(known, false, (before, descriptor) => {
|
|
231
238
|
const key = `${descriptor.kind}\u0000${pattern}`
|
|
232
239
|
return (
|
|
233
|
-
Option.getOrElse(
|
|
240
|
+
Option.getOrElse(Option.fromUndefinedOr(memo.fits.get(key)), () => {
|
|
234
241
|
// Each check of a pattern reads the fields of a catalog. The
|
|
235
242
|
// work of one binding has a limit, so very many patterns stop
|
|
236
243
|
// with one issue, and the policy does not load.
|
|
@@ -240,7 +247,7 @@ const patternIssues = (
|
|
|
240
247
|
return true
|
|
241
248
|
}
|
|
242
249
|
const found = Patterns.fitsCatalog(parsed, descriptor.catalog)
|
|
243
|
-
|
|
250
|
+
memo.fits.set(key, found)
|
|
244
251
|
return found
|
|
245
252
|
}) || before
|
|
246
253
|
)
|
|
@@ -282,6 +289,14 @@ const principalField = (
|
|
|
282
289
|
at: ReadonlyArray<PropertyKey>,
|
|
283
290
|
): Catalog.Field | Issue => {
|
|
284
291
|
const found = Catalog.lookup(context.domain.principalCatalog, ref)
|
|
292
|
+
// A read of the principal that is not plain data is a defect, as a read
|
|
293
|
+
// of a resource is: see `readIssues`.
|
|
294
|
+
if (found !== undefined && !found.plain) {
|
|
295
|
+
return {
|
|
296
|
+
path: at,
|
|
297
|
+
issue: `A condition may not read the field "${ref}" of the principal. A value above it may be an object that does not have it as a data field, as a class with a getter, a date or a list. Use a predicate for it.`,
|
|
298
|
+
}
|
|
299
|
+
}
|
|
285
300
|
return found !== undefined
|
|
286
301
|
? found
|
|
287
302
|
: {
|
|
@@ -577,8 +592,13 @@ export const ruleIssues = (
|
|
|
577
592
|
memo: Memo = makeMemo(),
|
|
578
593
|
): ReadonlyArray<Issue> => {
|
|
579
594
|
const all = rule.resources.includes("*")
|
|
580
|
-
// Each kind is checked one time, also when the list names it again.
|
|
581
|
-
|
|
595
|
+
// Each kind is checked one time, also when the list names it again. The
|
|
596
|
+
// names come from a policy, so a native `Set` finds the copies:
|
|
597
|
+
// `Arr.dedupe` puts the names in groups by the hash of Effect, which has
|
|
598
|
+
// no random seed, and very many names with one hash make one long group.
|
|
599
|
+
const names = Array.from(
|
|
600
|
+
new Set(Arr.filter(rule.resources, (kind) => kind !== "*")),
|
|
601
|
+
)
|
|
582
602
|
const kinds = Array.from(HashMap.keys(domain.resources))
|
|
583
603
|
const named = Arr.flatMap(names, (kind) =>
|
|
584
604
|
Option.toArray(HashMap.get(domain.resources, kind)),
|
|
@@ -599,8 +619,9 @@ export const ruleIssues = (
|
|
|
599
619
|
? Arr.filter(known, (descriptor) => hasAction(descriptor, rule.actions))
|
|
600
620
|
: applicable,
|
|
601
621
|
}
|
|
602
|
-
|
|
603
|
-
|
|
622
|
+
// A native `Set` finds copies about ten times as fast as `Arr.dedupe`.
|
|
623
|
+
const actions = Array.from(
|
|
624
|
+
new Set(Arr.flatMap(known, (descriptor) => descriptor.actions)),
|
|
604
625
|
)
|
|
605
626
|
return [
|
|
606
627
|
...Arr.map(
|
package/src/internal/catalog.ts
CHANGED
|
@@ -69,7 +69,9 @@ export const joinKinds = (left: Kinds, right: Kinds): Kinds => ({
|
|
|
69
69
|
? left.number
|
|
70
70
|
: right.number,
|
|
71
71
|
object: left.object || right.object,
|
|
72
|
-
|
|
72
|
+
// A native `Set` finds copies about ten times as fast as `Arr.dedupe`. It
|
|
73
|
+
// takes `0` and `-0` as one, as `includes` does when a test reads them.
|
|
74
|
+
literals: Array.from(new Set([...left.literals, ...right.literals])),
|
|
73
75
|
})
|
|
74
76
|
|
|
75
77
|
/** Tests if a number fits the numbers of a field. */
|
|
@@ -146,6 +148,12 @@ export interface Branch {
|
|
|
146
148
|
readonly field: Field | undefined
|
|
147
149
|
/** The places below, by name, in the order of the Schema. */
|
|
148
150
|
readonly children: ReadonlyArray<readonly [string, Branch]>
|
|
151
|
+
/**
|
|
152
|
+
* The places below, by name. A lookup gets each name from `split`, as a
|
|
153
|
+
* new string, and a `HashMap` finds a new string about 20% faster than a
|
|
154
|
+
* native `Map`. The names come from the Schema, so a name from a policy
|
|
155
|
+
* can not make a long list of names with one hash here.
|
|
156
|
+
*/
|
|
149
157
|
readonly names: HashMap.HashMap<string, Branch>
|
|
150
158
|
/** The place for the keys of a record, when the value may be a record. */
|
|
151
159
|
readonly key: Key | undefined
|
|
@@ -361,12 +369,12 @@ const nameOf = (signature: SchemaAST.PropertySignature): string | undefined =>
|
|
|
361
369
|
/**
|
|
362
370
|
* The properties of a struct by name. A walk makes it one time for each
|
|
363
371
|
* struct of a place, so a struct with very many fields does not take a
|
|
364
|
-
* search for each name.
|
|
372
|
+
* search for each name. It is a native `Map`, as `Branch.names` is.
|
|
365
373
|
*/
|
|
366
374
|
const propertiesOf = (
|
|
367
375
|
struct: SchemaAST.Objects,
|
|
368
|
-
):
|
|
369
|
-
|
|
376
|
+
): ReadonlyMap<string, SchemaAST.PropertySignature> =>
|
|
377
|
+
new Map(
|
|
370
378
|
Arr.flatMap(struct.propertySignatures, (signature) => {
|
|
371
379
|
const name = nameOf(signature)
|
|
372
380
|
return name === undefined ? [] : [[name, signature] as const]
|
|
@@ -377,25 +385,29 @@ const propertiesOf = (
|
|
|
377
385
|
const namesOf = (
|
|
378
386
|
structs: ReadonlyArray<SchemaAST.Objects>,
|
|
379
387
|
): ReadonlyArray<string> =>
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
388
|
+
// A native `Set` keeps the first copy of each name, in order, about ten
|
|
389
|
+
// times as fast as `Arr.dedupe`.
|
|
390
|
+
Array.from(
|
|
391
|
+
new Set(
|
|
392
|
+
Arr.flatMap(structs, (struct) =>
|
|
393
|
+
Arr.flatMap(struct.propertySignatures, (signature) => {
|
|
394
|
+
const name = nameOf(signature)
|
|
395
|
+
if (name === undefined) {
|
|
396
|
+
return []
|
|
397
|
+
}
|
|
398
|
+
// A dot in a path is the step into a nested field. A field name with
|
|
399
|
+
// a dot would make two fields have one path, so it is a defect.
|
|
400
|
+
if (name.includes(".")) {
|
|
401
|
+
throw new Error(`A field name may not have a dot: "${name}"`)
|
|
402
|
+
}
|
|
403
|
+
// An empty name would make an empty part in a path, such as
|
|
404
|
+
// `.secret`.
|
|
405
|
+
if (name === "") {
|
|
406
|
+
throw new Error("A field name may not be empty")
|
|
407
|
+
}
|
|
408
|
+
return [name]
|
|
409
|
+
}),
|
|
410
|
+
),
|
|
399
411
|
),
|
|
400
412
|
)
|
|
401
413
|
|
|
@@ -478,9 +490,10 @@ const branchOf = (place: Place, count: { size: number }): Branch => {
|
|
|
478
490
|
const structProperties = Arr.map(structs, propertiesOf)
|
|
479
491
|
const classProperties = Arr.map(classes, propertiesOf)
|
|
480
492
|
const children = Arr.map(namesOf(structs), (name) => {
|
|
481
|
-
const properties = Arr.flatMap(structProperties, (named) =>
|
|
482
|
-
|
|
483
|
-
|
|
493
|
+
const properties = Arr.flatMap(structProperties, (named) => {
|
|
494
|
+
const property = named.get(name)
|
|
495
|
+
return property === undefined ? [] : [property]
|
|
496
|
+
})
|
|
484
497
|
const below: Place = {
|
|
485
498
|
path: [...place.path, name],
|
|
486
499
|
schemas: Arr.flatMap(properties, (property) =>
|
|
@@ -500,7 +513,7 @@ const branchOf = (place: Place, count: { size: number }): Branch => {
|
|
|
500
513
|
plain:
|
|
501
514
|
place.plain &&
|
|
502
515
|
!valueKinds.object &&
|
|
503
|
-
Arr.every(classProperties, (named) =>
|
|
516
|
+
Arr.every(classProperties, (named) => named.has(name)),
|
|
504
517
|
}
|
|
505
518
|
return [name, branchOf(below, count)] as const
|
|
506
519
|
})
|
package/src/internal/json.ts
CHANGED
|
@@ -143,15 +143,6 @@ const decodeOperands = (
|
|
|
143
143
|
decodeOperand(operand, [...path, name, index]),
|
|
144
144
|
)
|
|
145
145
|
|
|
146
|
-
const compareOperators: ReadonlyArray<string> = [
|
|
147
|
-
"eq",
|
|
148
|
-
"ne",
|
|
149
|
-
"lt",
|
|
150
|
-
"lte",
|
|
151
|
-
"gt",
|
|
152
|
-
"gte",
|
|
153
|
-
]
|
|
154
|
-
|
|
155
146
|
const decodeNode = (
|
|
156
147
|
input: unknown,
|
|
157
148
|
path: ReadonlyArray<PropertyKey>,
|
|
@@ -199,11 +190,11 @@ const makeNode = (
|
|
|
199
190
|
return Nodes.never
|
|
200
191
|
case "Compare": {
|
|
201
192
|
const op = readString(input, "op", path)
|
|
202
|
-
if (!
|
|
193
|
+
if (!Nodes.isCompareOperator(op)) {
|
|
203
194
|
throw problem([...path, "op"], `"${op}" is not a compare operator`)
|
|
204
195
|
}
|
|
205
196
|
return new Nodes.Compare({
|
|
206
|
-
op
|
|
197
|
+
op,
|
|
207
198
|
field: readString(input, "field", path),
|
|
208
199
|
value: decodeOperand(read(input, "value", path), [...path, "value"]),
|
|
209
200
|
})
|
package/src/internal/nodes.ts
CHANGED
|
@@ -21,12 +21,31 @@ import * as Arr from "effect/Array"
|
|
|
21
21
|
import * as Data from "effect/Data"
|
|
22
22
|
import type * as Effect from "effect/Effect"
|
|
23
23
|
import { hasProperty } from "effect/Predicate"
|
|
24
|
+
import * as Schema from "effect/Schema"
|
|
24
25
|
import * as Limits from "./limits.ts"
|
|
25
26
|
|
|
26
27
|
/** A value that a condition can compare. */
|
|
27
28
|
export type Scalar = string | number | boolean | null
|
|
28
29
|
|
|
29
|
-
|
|
30
|
+
/** The operators of a `Compare` test. The decoders read this list too. */
|
|
31
|
+
export const CompareOperator = Schema.Literals([
|
|
32
|
+
"eq",
|
|
33
|
+
"ne",
|
|
34
|
+
"lt",
|
|
35
|
+
"lte",
|
|
36
|
+
"gt",
|
|
37
|
+
"gte",
|
|
38
|
+
])
|
|
39
|
+
|
|
40
|
+
export type CompareOperator = typeof CompareOperator.Type
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Tests if a value is an operator. It reads the list of the Schema with
|
|
44
|
+
* `includes`: for a short list, this is about twice as fast as
|
|
45
|
+
* `Schema.is`, and a decoder tests each node of a large input.
|
|
46
|
+
*/
|
|
47
|
+
export const isCompareOperator = (u: unknown): u is CompareOperator =>
|
|
48
|
+
(CompareOperator.literals as ReadonlyArray<unknown>).includes(u)
|
|
30
49
|
|
|
31
50
|
/** A fixed value. */
|
|
32
51
|
export interface Literal {
|
|
@@ -49,15 +68,6 @@ export const literal = (value: Scalar): Literal => operands.Literal({ value })
|
|
|
49
68
|
export const principalRef = (path: string): PrincipalRef =>
|
|
50
69
|
operands.Principal({ path })
|
|
51
70
|
|
|
52
|
-
const compareOperators: ReadonlyArray<string> = [
|
|
53
|
-
"eq",
|
|
54
|
-
"ne",
|
|
55
|
-
"lt",
|
|
56
|
-
"lte",
|
|
57
|
-
"gt",
|
|
58
|
-
"gte",
|
|
59
|
-
]
|
|
60
|
-
|
|
61
71
|
/** Tests if a value is a scalar. Any number is one, also `NaN`. */
|
|
62
72
|
export const isScalar = (value: unknown): value is Scalar =>
|
|
63
73
|
value === null ||
|
|
@@ -211,7 +221,7 @@ export class Compare extends Base {
|
|
|
211
221
|
readonly field: string
|
|
212
222
|
readonly value: Operand
|
|
213
223
|
}) {
|
|
214
|
-
if (!
|
|
224
|
+
if (!isCompareOperator(props.op)) {
|
|
215
225
|
throw new Error(`"${String(props.op)}" is not a compare operator`)
|
|
216
226
|
}
|
|
217
227
|
super(
|
package/src/internal/patterns.ts
CHANGED
|
@@ -117,9 +117,13 @@ type States = ReadonlyArray<number>
|
|
|
117
117
|
|
|
118
118
|
/** Adds the place after each `**`, since a `**` may match no names. */
|
|
119
119
|
const close = (pattern: Pattern, states: States): States =>
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
120
|
+
// A native `Set` keeps the first copy of each place, in order. It is
|
|
121
|
+
// faster than `Arr.dedupe`, and this is on the path of each match.
|
|
122
|
+
Array.from(
|
|
123
|
+
new Set(
|
|
124
|
+
Arr.flatMap(states, (state) =>
|
|
125
|
+
pattern[state]?._tag === "Globstar" ? [state, state + 1] : [state],
|
|
126
|
+
),
|
|
123
127
|
),
|
|
124
128
|
)
|
|
125
129
|
|
|
@@ -136,13 +136,6 @@ export const encode = (
|
|
|
136
136
|
return toJson(node, (rule) => forms.get(rule))
|
|
137
137
|
})
|
|
138
138
|
|
|
139
|
-
const algorithms: ReadonlyArray<string> = [
|
|
140
|
-
"denyOverrides",
|
|
141
|
-
"permitOverrides",
|
|
142
|
-
"firstApplicable",
|
|
143
|
-
"lastApplicable",
|
|
144
|
-
]
|
|
145
|
-
|
|
146
139
|
/** Reads a list of strings. */
|
|
147
140
|
const readStrings = (
|
|
148
141
|
input: object,
|
|
@@ -374,7 +367,7 @@ const readTree = (
|
|
|
374
367
|
switch (tag) {
|
|
375
368
|
case "Rules": {
|
|
376
369
|
const algorithm = Json.readString(input, "algorithm", path)
|
|
377
|
-
if (!
|
|
370
|
+
if (!PolicyNodes.isAlgorithm(algorithm)) {
|
|
378
371
|
throw Json.problem(
|
|
379
372
|
[...path, "algorithm"],
|
|
380
373
|
`"${algorithm}" is not a combining algorithm`,
|
|
@@ -392,7 +385,7 @@ const readTree = (
|
|
|
392
385
|
_tag: "Rules",
|
|
393
386
|
place,
|
|
394
387
|
size: 1 + found.length,
|
|
395
|
-
algorithm
|
|
388
|
+
algorithm,
|
|
396
389
|
rules: found,
|
|
397
390
|
})
|
|
398
391
|
}
|
|
@@ -11,19 +11,27 @@
|
|
|
11
11
|
*/
|
|
12
12
|
import * as Arr from "effect/Array"
|
|
13
13
|
import * as Data from "effect/Data"
|
|
14
|
+
import * as Schema from "effect/Schema"
|
|
14
15
|
import * as Rule from "./rule.ts"
|
|
15
16
|
import * as Limits from "./limits.ts"
|
|
16
17
|
import * as Nodes from "./nodes.ts"
|
|
17
18
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
const algorithms: ReadonlyArray<string> = [
|
|
19
|
+
/** The combining algorithms of a list of rules. The decoder reads this list too. */
|
|
20
|
+
export const Algorithm = Schema.Literals([
|
|
22
21
|
"denyOverrides",
|
|
23
22
|
"permitOverrides",
|
|
24
23
|
"firstApplicable",
|
|
25
24
|
"lastApplicable",
|
|
26
|
-
]
|
|
25
|
+
])
|
|
26
|
+
|
|
27
|
+
export type Algorithm = typeof Algorithm.Type
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Tests if a value is a combining algorithm. It reads the list of the Schema
|
|
31
|
+
* with `includes`, as `Nodes.isCompareOperator` does.
|
|
32
|
+
*/
|
|
33
|
+
export const isAlgorithm = (u: unknown): u is Algorithm =>
|
|
34
|
+
(Algorithm.literals as ReadonlyArray<unknown>).includes(u)
|
|
27
35
|
|
|
28
36
|
interface Facts {
|
|
29
37
|
readonly size: number
|
|
@@ -103,7 +111,7 @@ export class Rules extends Base {
|
|
|
103
111
|
readonly algorithm: Algorithm
|
|
104
112
|
readonly rules: ReadonlyArray<Rule.Rule>
|
|
105
113
|
}) {
|
|
106
|
-
if (!
|
|
114
|
+
if (!isAlgorithm(props.algorithm)) {
|
|
107
115
|
throw new Error(
|
|
108
116
|
`"${String(props.algorithm)}" is not a combining algorithm`,
|
|
109
117
|
)
|
package/src/internal/rule.ts
CHANGED
|
@@ -7,8 +7,6 @@
|
|
|
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"
|
|
12
10
|
import * as Condition from "../Condition.ts"
|
|
13
11
|
import type * as Domain from "../Domain.ts"
|
|
14
12
|
import type * as Policy from "../Policy.ts"
|
|
@@ -170,13 +168,19 @@ export const matchesInside = (pattern: string, field: string): boolean =>
|
|
|
170
168
|
export const fitsOf = (
|
|
171
169
|
catalog: Catalog.Catalog,
|
|
172
170
|
): ((pattern: string) => boolean) => {
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
171
|
+
// A native `Map`: the hash of a string in Effect has no random seed, and a
|
|
172
|
+
// policy could hold very many patterns with one hash. The engine hashes a
|
|
173
|
+
// string with a random seed.
|
|
174
|
+
const memo = new Map<string, boolean>()
|
|
175
|
+
return (pattern) => {
|
|
176
|
+
const known = memo.get(pattern)
|
|
177
|
+
if (known !== undefined) {
|
|
178
|
+
return known
|
|
179
|
+
}
|
|
180
|
+
const found = Patterns.fitsCatalog(Patterns.parseBound(pattern), catalog)
|
|
181
|
+
memo.set(pattern, found)
|
|
182
|
+
return found
|
|
183
|
+
}
|
|
180
184
|
}
|
|
181
185
|
|
|
182
186
|
/**
|