@speedkit/cli 4.26.0 → 4.27.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/CHANGELOG.md +10 -0
- package/README.md +186 -15
- package/dist/commands/auto-prewarm.d.ts +8 -1
- package/dist/commands/auto-prewarm.js +18 -7
- package/dist/commands/generate-pop-config.d.ts +1 -0
- package/dist/commands/generate-pop-config.js +9 -3
- package/dist/commands/prewarm.js +3 -1
- package/dist/commands/query/parameter.d.ts +26 -0
- package/dist/commands/query/parameter.js +27 -0
- package/dist/commands/query/prewarm.d.ts +31 -0
- package/dist/commands/query/prewarm.js +31 -0
- package/dist/helpers/clipboard.d.ts +19 -0
- package/dist/helpers/clipboard.js +70 -0
- package/dist/helpers/evaluate-speed-kit-config.d.ts +8 -18
- package/dist/helpers/evaluate-speed-kit-config.js +8 -6
- package/dist/helpers/evaluate-speed-kit-config.spec.d.ts +1 -0
- package/dist/helpers/evaluate-speed-kit-config.spec.js +78 -0
- package/dist/models/cli-parameters.d.ts +73 -0
- package/dist/models/cli-parameters.js +151 -0
- package/dist/models/cli-parameters.spec.d.ts +1 -0
- package/dist/models/cli-parameters.spec.js +47 -0
- package/dist/services/athena/athena-service.d.ts +2 -0
- package/dist/services/athena/athena-service.js +15 -4
- package/dist/services/athena/athena-service.spec.d.ts +1 -0
- package/dist/services/athena/athena-service.spec.js +74 -0
- package/dist/services/deploy/handler/install-resource-handler.js +3 -4
- package/dist/services/onboarding/onboarding-service-factory.js +2 -2
- package/dist/services/pop-config/pop-config-service.d.ts +8 -1
- package/dist/services/pop-config/pop-config-service.js +16 -28
- package/dist/services/prewarm/assets/asset-api-client.d.ts +1 -1
- package/dist/services/prewarm/assets/asset-api-client.js +5 -4
- package/dist/services/prewarm/auto-pre-warm-factory.d.ts +12 -2
- package/dist/services/prewarm/auto-pre-warm-factory.js +20 -6
- package/dist/services/prewarm/csv-reader.d.ts +21 -2
- package/dist/services/prewarm/csv-reader.js +71 -9
- package/dist/services/prewarm/csv-reader.spec.d.ts +1 -0
- package/dist/services/prewarm/csv-reader.spec.js +75 -0
- package/dist/services/prewarm/index.d.ts +1 -0
- package/dist/services/prewarm/index.js +1 -0
- package/dist/services/prewarm/pre-warm-factory.d.ts +0 -2
- package/dist/services/prewarm/pre-warm-factory.js +8 -11
- package/dist/services/prewarm/pre-warm-model.d.ts +14 -1
- package/dist/services/prewarm/pre-warm-model.js +0 -1
- package/dist/services/prewarm/pre-warm-service.d.ts +9 -4
- package/dist/services/prewarm/pre-warm-service.js +18 -14
- package/dist/services/prewarm/prewarm-targets.d.ts +12 -0
- package/dist/services/prewarm/prewarm-targets.js +18 -0
- package/dist/services/prewarm/prewarm-targets.spec.d.ts +1 -0
- package/dist/services/prewarm/prewarm-targets.spec.js +29 -0
- package/dist/services/query-builder/error/match-matches-nothing-error.d.ts +11 -0
- package/dist/services/query-builder/error/match-matches-nothing-error.js +17 -0
- package/dist/services/query-builder/queries/device.d.ts +21 -0
- package/dist/services/query-builder/queries/device.js +26 -0
- package/dist/services/query-builder/queries/index.d.ts +6 -0
- package/dist/services/query-builder/queries/index.js +6 -0
- package/dist/services/query-builder/queries/page-filter.d.ts +39 -0
- package/dist/services/query-builder/queries/page-filter.js +135 -0
- package/dist/services/query-builder/queries/page-source.d.ts +30 -0
- package/dist/services/query-builder/queries/page-source.js +79 -0
- package/dist/services/query-builder/queries/parameter-query.d.ts +14 -0
- package/dist/services/query-builder/queries/parameter-query.js +142 -0
- package/dist/services/query-builder/queries/pops-query.d.ts +27 -0
- package/dist/services/query-builder/queries/pops-query.js +65 -0
- package/dist/services/query-builder/queries/prewarm-query.d.ts +18 -0
- package/dist/services/query-builder/queries/prewarm-query.js +227 -0
- package/dist/services/query-builder/queries/query-model.d.ts +97 -0
- package/dist/services/query-builder/queries/query-model.js +19 -0
- package/dist/services/query-builder/queries/spec/golden-configs.d.ts +18 -0
- package/dist/services/query-builder/queries/spec/golden-configs.js +116 -0
- package/dist/services/query-builder/queries/spec/golden.spec.d.ts +1 -0
- package/dist/services/query-builder/queries/spec/golden.spec.js +161 -0
- package/dist/services/query-builder/queries/spec/pops-query.spec.d.ts +1 -0
- package/dist/services/query-builder/queries/spec/pops-query.spec.js +58 -0
- package/dist/services/query-builder/queries/spec/queries.spec.d.ts +1 -0
- package/dist/services/query-builder/queries/spec/queries.spec.js +312 -0
- package/dist/services/query-builder/queries/spec/reported-bugs.spec.d.ts +1 -0
- package/dist/services/query-builder/queries/spec/reported-bugs.spec.js +86 -0
- package/dist/services/query-builder/query-builder-factory.d.ts +2 -2
- package/dist/services/query-builder/query-builder-factory.js +35 -7
- package/dist/services/query-builder/query-builder-model.d.ts +31 -19
- package/dist/services/query-builder/query-builder-model.js +5 -5
- package/dist/services/query-builder/query-builder-service.d.ts +26 -5
- package/dist/services/query-builder/query-builder-service.js +157 -44
- package/dist/services/query-builder/query-command.d.ts +26 -0
- package/dist/services/query-builder/query-command.js +35 -0
- package/dist/services/query-builder/rules/index.d.ts +8 -0
- package/dist/services/query-builder/rules/index.js +8 -0
- package/dist/services/query-builder/rules/match-pattern.d.ts +51 -0
- package/dist/services/query-builder/rules/match-pattern.js +141 -0
- package/dist/services/query-builder/rules/pattern-language.d.ts +59 -0
- package/dist/services/query-builder/rules/pattern-language.js +159 -0
- package/dist/services/query-builder/rules/rule-model.d.ts +107 -0
- package/dist/services/query-builder/rules/rule-model.js +54 -0
- package/dist/services/query-builder/rules/rule-parser.d.ts +33 -0
- package/dist/services/query-builder/rules/rule-parser.js +189 -0
- package/dist/services/query-builder/rules/rule-reducer.d.ts +71 -0
- package/dist/services/query-builder/rules/rule-reducer.js +383 -0
- package/dist/services/query-builder/rules/rule-relevance.d.ts +43 -0
- package/dist/services/query-builder/rules/rule-relevance.js +69 -0
- package/dist/services/query-builder/rules/rule-to-sql.d.ts +51 -0
- package/dist/services/query-builder/rules/rule-to-sql.js +75 -0
- package/dist/services/query-builder/rules/spec/pattern-language.spec.d.ts +1 -0
- package/dist/services/query-builder/rules/spec/pattern-language.spec.js +90 -0
- package/dist/services/query-builder/rules/spec/rule-parser.spec.d.ts +1 -0
- package/dist/services/query-builder/rules/spec/rule-parser.spec.js +150 -0
- package/dist/services/query-builder/rules/spec/rule-reducer.spec.d.ts +1 -0
- package/dist/services/query-builder/rules/spec/rule-reducer.spec.js +169 -0
- package/dist/services/query-builder/rules/spec/rule-relevance.spec.d.ts +1 -0
- package/dist/services/query-builder/rules/spec/rule-relevance.spec.js +88 -0
- package/dist/services/query-builder/rules/spec/rule-to-sql.spec.d.ts +1 -0
- package/dist/services/query-builder/rules/spec/rule-to-sql.spec.js +110 -0
- package/dist/services/query-builder/rules/spec/runtime-semantics.spec.d.ts +1 -0
- package/dist/services/query-builder/rules/spec/runtime-semantics.spec.js +55 -0
- package/dist/services/query-builder/rules/spec/strip-parameters.spec.d.ts +1 -0
- package/dist/services/query-builder/rules/spec/strip-parameters.spec.js +85 -0
- package/dist/services/query-builder/rules/strip-parameters.d.ts +39 -0
- package/dist/services/query-builder/rules/strip-parameters.js +197 -0
- package/dist/services/query-builder/sql/expression.d.ts +73 -0
- package/dist/services/query-builder/sql/expression.js +133 -0
- package/dist/services/query-builder/sql/format.d.ts +14 -0
- package/dist/services/query-builder/sql/format.js +48 -0
- package/dist/services/query-builder/sql/index.d.ts +4 -0
- package/dist/services/query-builder/sql/index.js +4 -0
- package/dist/services/query-builder/sql/predicate.d.ts +66 -0
- package/dist/services/query-builder/sql/predicate.js +124 -0
- package/dist/services/query-builder/sql/regex.d.ts +28 -0
- package/dist/services/query-builder/sql/regex.js +81 -0
- package/dist/services/query-builder/sql/spec/expression.spec.d.ts +1 -0
- package/dist/services/query-builder/sql/spec/expression.spec.js +75 -0
- package/dist/services/query-builder/sql/spec/format.spec.d.ts +1 -0
- package/dist/services/query-builder/sql/spec/format.spec.js +37 -0
- package/dist/services/query-builder/sql/spec/predicate.spec.d.ts +1 -0
- package/dist/services/query-builder/sql/spec/predicate.spec.js +78 -0
- package/dist/services/query-builder/sql/spec/regex.spec.d.ts +1 -0
- package/dist/services/query-builder/sql/spec/regex.spec.js +62 -0
- package/oclif.manifest.json +350 -100
- package/package.json +11 -2
- package/dist/commands/build-parameter-query.d.ts +0 -14
- package/dist/commands/build-parameter-query.js +0 -25
- package/dist/commands/build-prewarm-query.d.ts +0 -16
- package/dist/commands/build-prewarm-query.js +0 -37
- package/dist/helpers/build-query-helper.d.ts +0 -32
- package/dist/helpers/build-query-helper.js +0 -223
- package/dist/helpers/get-parsed-config.d.ts +0 -15
- package/dist/helpers/get-parsed-config.js +0 -62
- package/dist/services/onboarding/dashboard/parameter-query-builder.d.ts +0 -8
- package/dist/services/onboarding/dashboard/parameter-query-builder.js +0 -63
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
import { types } from "node:util";
|
|
2
|
+
import { ConditionKey } from "@speedkit/rules";
|
|
3
|
+
import { escapeRegexLiteral } from "../sql/index.js";
|
|
4
|
+
import { CRITERIA_OUTSIDE_THE_URL, DEFAULT_RULE_SET_VERSION, } from "./rule-model.js";
|
|
5
|
+
const SUBJECTS = ["host", "pathname", "url"];
|
|
6
|
+
/** Reads the rule sets a query is built from out of an evaluated config. */
|
|
7
|
+
export function parseRuleSets(config) {
|
|
8
|
+
const speedKitConfig = asRecord(config) ?? {};
|
|
9
|
+
const ruleSetVersion = parseRuleSetVersion(speedKitConfig);
|
|
10
|
+
return {
|
|
11
|
+
blacklist: parseRules(speedKitConfig.blacklist, "blacklist", ruleSetVersion),
|
|
12
|
+
disabledScopes: parseDisabledScopes(speedKitConfig.disabledScopes),
|
|
13
|
+
disabledSites: parseRules(speedKitConfig.disabledSites, "disabledSites", ruleSetVersion),
|
|
14
|
+
enabledSites: parseRules(speedKitConfig.enabledSites, "enabledSites", ruleSetVersion),
|
|
15
|
+
ruleSetVersion,
|
|
16
|
+
stripQueryParams: parseStripQueryParams(speedKitConfig.stripQueryParams, ruleSetVersion),
|
|
17
|
+
whitelist: parseRules(speedKitConfig.whitelist, "whitelist", ruleSetVersion),
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
/** The declared rule-set version, or 1 for a config that declares none. */
|
|
21
|
+
export function parseRuleSetVersion(config) {
|
|
22
|
+
const ruleSetConfig = asRecord(asRecord(config)?.ruleSetConfig);
|
|
23
|
+
const version = Number(ruleSetConfig?.version);
|
|
24
|
+
return Number.isFinite(version) && version > 0
|
|
25
|
+
? version
|
|
26
|
+
: DEFAULT_RULE_SET_VERSION;
|
|
27
|
+
}
|
|
28
|
+
/** Normalizes a `SpeedKitRule[]` — anything else yields no rules. */
|
|
29
|
+
export function parseRules(value, source, ruleSetVersion) {
|
|
30
|
+
if (!Array.isArray(value)) {
|
|
31
|
+
return [];
|
|
32
|
+
}
|
|
33
|
+
const rules = [];
|
|
34
|
+
for (const [index, entry] of value.entries()) {
|
|
35
|
+
const rule = parseRule(entry, `${source}[${index}]`, ruleSetVersion);
|
|
36
|
+
if (rule) {
|
|
37
|
+
rules.push(rule);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
return rules;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Normalizes a `Condition` — a string, a regular expression, or an array of
|
|
44
|
+
* both — into a flat list of patterns.
|
|
45
|
+
*/
|
|
46
|
+
export function parseCondition(value) {
|
|
47
|
+
if (value === undefined || value === null) {
|
|
48
|
+
return [];
|
|
49
|
+
}
|
|
50
|
+
const entries = Array.isArray(value) ? value : [value];
|
|
51
|
+
const patterns = [];
|
|
52
|
+
for (const entry of entries) {
|
|
53
|
+
// The config is evaluated in a vm context, so its regular expressions come
|
|
54
|
+
// from another realm: `instanceof RegExp` is false for them here, and it
|
|
55
|
+
// is false again in `testCondition`. Rebuilding them in this realm is what
|
|
56
|
+
// makes every later check see a regular expression.
|
|
57
|
+
if (types.isRegExp(entry)) {
|
|
58
|
+
const { flags, source } = entry;
|
|
59
|
+
patterns.push({ kind: "regexp", value: new RegExp(source, flags) });
|
|
60
|
+
continue;
|
|
61
|
+
}
|
|
62
|
+
if (typeof entry === "string" || typeof entry === "number") {
|
|
63
|
+
patterns.push({ kind: "string", value: String(entry) });
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
return patterns;
|
|
67
|
+
}
|
|
68
|
+
function parseRule(value, source, ruleSetVersion) {
|
|
69
|
+
const rule = asRecord(value);
|
|
70
|
+
if (!rule) {
|
|
71
|
+
return undefined;
|
|
72
|
+
}
|
|
73
|
+
const conditions = [];
|
|
74
|
+
for (const subject of SUBJECTS) {
|
|
75
|
+
const patterns = parseCondition(rule[subject]);
|
|
76
|
+
if (patterns.length > 0) {
|
|
77
|
+
conditions.push({ patterns, subject });
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
const version = Number(rule.ruleSetVersion);
|
|
81
|
+
const keys = Object.values(ConditionKey).filter((key) => rule[key] !== undefined);
|
|
82
|
+
return {
|
|
83
|
+
conditions,
|
|
84
|
+
matchesNothing: keys.length === 0 ||
|
|
85
|
+
// An empty condition is satisfied by nothing, and every condition of a
|
|
86
|
+
// rule has to hold.
|
|
87
|
+
keys.some((key) => Array.isArray(rule[key]) && rule[key].length === 0),
|
|
88
|
+
contentTypes: parseContentTypes(rule.contentType),
|
|
89
|
+
criteriaOutsideTheUrl: CRITERIA_OUTSIDE_THE_URL.filter((criterion) => rule[criterion] !== undefined),
|
|
90
|
+
ruleSetVersion: Number.isFinite(version) && version > 0 ? version : ruleSetVersion,
|
|
91
|
+
source,
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
function parseContentTypes(value) {
|
|
95
|
+
if (value === undefined || value === null) {
|
|
96
|
+
return undefined;
|
|
97
|
+
}
|
|
98
|
+
const entries = Array.isArray(value) ? value : [value];
|
|
99
|
+
const contentTypes = entries
|
|
100
|
+
.filter((entry) => typeof entry === "string")
|
|
101
|
+
.map((entry) => entry.toLowerCase());
|
|
102
|
+
return contentTypes.length > 0 ? contentTypes : undefined;
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* Turns `disabledScopes` into rules.
|
|
106
|
+
*
|
|
107
|
+
* A scope is a service-worker scope, not a Speed Kit condition: a
|
|
108
|
+
* case-sensitive path prefix, matched on the URL string with no regular
|
|
109
|
+
* expression and no wildcard, so `/checkout` covers `/checkout/payment` and
|
|
110
|
+
* `/checkoutTunnel` but not `/en/checkout`. Written as an anchored pattern it
|
|
111
|
+
* says exactly that, and it can then be read, reduced and translated like
|
|
112
|
+
* every other rule.
|
|
113
|
+
*
|
|
114
|
+
* Pages under a scope are served by an empty service worker, so Speed Kit
|
|
115
|
+
* never touches them — they belong in neither a pre-warm list nor a parameter
|
|
116
|
+
* analysis.
|
|
117
|
+
*/
|
|
118
|
+
export function parseDisabledScopes(value) {
|
|
119
|
+
if (!Array.isArray(value)) {
|
|
120
|
+
return [];
|
|
121
|
+
}
|
|
122
|
+
const rules = [];
|
|
123
|
+
for (const [index, entry] of value.entries()) {
|
|
124
|
+
if (typeof entry !== "string" || entry.length === 0) {
|
|
125
|
+
continue;
|
|
126
|
+
}
|
|
127
|
+
rules.push({
|
|
128
|
+
conditions: [
|
|
129
|
+
{
|
|
130
|
+
patterns: [
|
|
131
|
+
{
|
|
132
|
+
kind: "regexp",
|
|
133
|
+
value: new RegExp(`^${escapeRegexLiteral(entry)}`),
|
|
134
|
+
},
|
|
135
|
+
],
|
|
136
|
+
subject: "pathname",
|
|
137
|
+
},
|
|
138
|
+
],
|
|
139
|
+
criteriaOutsideTheUrl: [],
|
|
140
|
+
matchesNothing: false,
|
|
141
|
+
// Version 2 keeps the query string out of the subject, which is what a
|
|
142
|
+
// path prefix is matched against.
|
|
143
|
+
ruleSetVersion: 2,
|
|
144
|
+
source: `disabledScopes[${index}]`,
|
|
145
|
+
});
|
|
146
|
+
}
|
|
147
|
+
return rules;
|
|
148
|
+
}
|
|
149
|
+
/**
|
|
150
|
+
* Normalizes `stripQueryParams`, which is either a bare condition (every
|
|
151
|
+
* parameter matching it is stripped everywhere) or a list of rules that scope
|
|
152
|
+
* the stripping to certain pages or resources.
|
|
153
|
+
*/
|
|
154
|
+
export function parseStripQueryParams(value, ruleSetVersion) {
|
|
155
|
+
if (value === undefined || value === null) {
|
|
156
|
+
return [];
|
|
157
|
+
}
|
|
158
|
+
const entries = Array.isArray(value) ? value : [value];
|
|
159
|
+
// A bare condition: strings and regular expressions, no scoping.
|
|
160
|
+
const bare = entries.filter((entry) => typeof entry === "string" || entry instanceof RegExp);
|
|
161
|
+
if (bare.length === entries.length) {
|
|
162
|
+
const params = parseCondition(bare);
|
|
163
|
+
return params.length > 0
|
|
164
|
+
? [{ params, ruleSetVersion, source: "stripQueryParams" }]
|
|
165
|
+
: [];
|
|
166
|
+
}
|
|
167
|
+
const rules = [];
|
|
168
|
+
for (const [index, entry] of entries.entries()) {
|
|
169
|
+
const rule = asRecord(entry);
|
|
170
|
+
const params = parseCondition(rule?.params);
|
|
171
|
+
if (!rule || params.length === 0) {
|
|
172
|
+
continue;
|
|
173
|
+
}
|
|
174
|
+
const source = `stripQueryParams[${index}]`;
|
|
175
|
+
rules.push({
|
|
176
|
+
enabledSites: parseRules(rule.enabledSites, `${source}.enabledSites`, ruleSetVersion),
|
|
177
|
+
params,
|
|
178
|
+
rules: parseRules(rule.rules, `${source}.rules`, ruleSetVersion),
|
|
179
|
+
ruleSetVersion,
|
|
180
|
+
source,
|
|
181
|
+
});
|
|
182
|
+
}
|
|
183
|
+
return rules;
|
|
184
|
+
}
|
|
185
|
+
function asRecord(value) {
|
|
186
|
+
return typeof value === "object" && value !== null && !Array.isArray(value)
|
|
187
|
+
? value
|
|
188
|
+
: undefined;
|
|
189
|
+
}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { NormalizedRule } from "./rule-model.js";
|
|
2
|
+
import { RuleNote } from "./rule-relevance.js";
|
|
3
|
+
/**
|
|
4
|
+
* How much comparing the reductions may do in total.
|
|
5
|
+
*
|
|
6
|
+
* Deciding one pattern against another is cheap for the patterns configs
|
|
7
|
+
* normally use, but a handful of them build automata whose determinisation
|
|
8
|
+
* costs seconds. Reducing is an optimisation — a query that keeps a redundant
|
|
9
|
+
* rule is still correct — so the work is capped.
|
|
10
|
+
*
|
|
11
|
+
* The cap counts comparisons rather than milliseconds. A clock makes the
|
|
12
|
+
* result depend on how busy the machine was: the same config would reduce
|
|
13
|
+
* further on a quiet laptop than in CI, and two runs would disagree about a
|
|
14
|
+
* query nobody changed. Counting the work makes the output a function of the
|
|
15
|
+
* config alone, which is what a golden query and a snapshot diff need.
|
|
16
|
+
*/
|
|
17
|
+
export interface ReductionBudget {
|
|
18
|
+
/** True once no comparison is left. */
|
|
19
|
+
exhausted: () => boolean;
|
|
20
|
+
/** Takes one comparison from the budget; false when there was none left. */
|
|
21
|
+
spend: () => boolean;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* The default budget for one query build.
|
|
25
|
+
*
|
|
26
|
+
* Calibrated against the configs in the onboarding repo: a config whose rules
|
|
27
|
+
* can be reduced spends far less than this — the largest reduction observed
|
|
28
|
+
* needs a few hundred — while the pathological ones, whose automata blow up on
|
|
29
|
+
* determinisation, are stopped before the command starts to feel slow.
|
|
30
|
+
*/
|
|
31
|
+
export declare const BUDGET_COMPARISONS = 800;
|
|
32
|
+
/** A budget of that many comparisons. */
|
|
33
|
+
export declare function reductionBudget(comparisons?: number): ReductionBudget;
|
|
34
|
+
export interface ReductionResult {
|
|
35
|
+
/** What was reduced away, and what made it redundant. */
|
|
36
|
+
readonly notes: RuleNote[];
|
|
37
|
+
readonly rules: NormalizedRule[];
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* The rules of one set, with everything a sibling already covers removed.
|
|
41
|
+
*
|
|
42
|
+
* Every rule set is a disjunction, and so is the entry list of a condition, so
|
|
43
|
+
* whatever is contained in something next to it contributes nothing: `a or b`
|
|
44
|
+
* is `b` when `a` implies `b`. Dropping it cannot change the result — it only
|
|
45
|
+
* shortens the query, which is the point.
|
|
46
|
+
*/
|
|
47
|
+
export declare function absorbRules(rules: readonly NormalizedRule[], budget?: ReductionBudget): ReductionResult;
|
|
48
|
+
/**
|
|
49
|
+
* The rules of an excluding set — `blacklist`, `disabledSites` — with
|
|
50
|
+
* everything that can never fire removed.
|
|
51
|
+
*
|
|
52
|
+
* A page has to pass every including set to reach the result at all, so an
|
|
53
|
+
* entry excluding pages none of them lets through excludes nothing. This is
|
|
54
|
+
* the bulk of what makes a generated query long: a config guards checkout,
|
|
55
|
+
* account and login while `enabledSites` only ever allowed the homepage and
|
|
56
|
+
* product pages. Configs write those guards as entries of a single rule, so
|
|
57
|
+
* the reduction works per entry and drops the rule only once a condition has
|
|
58
|
+
* no entry left that could match.
|
|
59
|
+
*
|
|
60
|
+
* @param reachable - The including sets, each a disjunction, all of which have
|
|
61
|
+
* to hold. An empty set means "everything" and is left out by the caller.
|
|
62
|
+
*/
|
|
63
|
+
export declare function dropUnreachableRules(rules: readonly NormalizedRule[], reachable: readonly (readonly NormalizedRule[])[], budget?: ReductionBudget): ReductionResult;
|
|
64
|
+
/**
|
|
65
|
+
* Rules of an including set that an excluding rule covers completely — pages
|
|
66
|
+
* the config means to accelerate but never will.
|
|
67
|
+
*
|
|
68
|
+
* Unlike the reductions above this changes nothing about the query: it is a
|
|
69
|
+
* finding about the config, and the caller reports it.
|
|
70
|
+
*/
|
|
71
|
+
export declare function findShadowedRules(included: readonly NormalizedRule[], excluding: readonly NormalizedRule[], budget?: ReductionBudget): RuleNote[];
|
|
@@ -0,0 +1,383 @@
|
|
|
1
|
+
import { conditionLanguage, isInside, languagesDisjoint, outsideOf, } from "./pattern-language.js";
|
|
2
|
+
import { resolvedSubject, } from "./rule-model.js";
|
|
3
|
+
/**
|
|
4
|
+
* The default budget for one query build.
|
|
5
|
+
*
|
|
6
|
+
* Calibrated against the configs in the onboarding repo: a config whose rules
|
|
7
|
+
* can be reduced spends far less than this — the largest reduction observed
|
|
8
|
+
* needs a few hundred — while the pathological ones, whose automata blow up on
|
|
9
|
+
* determinisation, are stopped before the command starts to feel slow.
|
|
10
|
+
*/
|
|
11
|
+
export const BUDGET_COMPARISONS = 800;
|
|
12
|
+
/** A budget of that many comparisons. */
|
|
13
|
+
export function reductionBudget(comparisons = BUDGET_COMPARISONS) {
|
|
14
|
+
let left = Math.max(0, Math.floor(comparisons));
|
|
15
|
+
return {
|
|
16
|
+
exhausted: () => left <= 0,
|
|
17
|
+
spend: () => left-- > 0,
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
/** A budget that never runs out. */
|
|
21
|
+
const UNLIMITED = {
|
|
22
|
+
exhausted: () => false,
|
|
23
|
+
spend: () => true,
|
|
24
|
+
};
|
|
25
|
+
/** The note left behind when the budget ran out. */
|
|
26
|
+
const STOPPED_EARLY = {
|
|
27
|
+
reason: `stopped after ${BUDGET_COMPARISONS} comparisons — rules that could still be reduced were kept`,
|
|
28
|
+
source: "reduction",
|
|
29
|
+
};
|
|
30
|
+
/** Every subject a query can provide. */
|
|
31
|
+
const SUBJECTS = [
|
|
32
|
+
"host",
|
|
33
|
+
"path",
|
|
34
|
+
"pathWithSearch",
|
|
35
|
+
"url",
|
|
36
|
+
];
|
|
37
|
+
/**
|
|
38
|
+
* The rules of one set, with everything a sibling already covers removed.
|
|
39
|
+
*
|
|
40
|
+
* Every rule set is a disjunction, and so is the entry list of a condition, so
|
|
41
|
+
* whatever is contained in something next to it contributes nothing: `a or b`
|
|
42
|
+
* is `b` when `a` implies `b`. Dropping it cannot change the result — it only
|
|
43
|
+
* shortens the query, which is the point.
|
|
44
|
+
*/
|
|
45
|
+
export function absorbRules(rules, budget = UNLIMITED) {
|
|
46
|
+
const notes = [];
|
|
47
|
+
const kept = [];
|
|
48
|
+
for (const [index, rule] of rules.entries()) {
|
|
49
|
+
if (budget.exhausted()) {
|
|
50
|
+
notes.push(STOPPED_EARLY);
|
|
51
|
+
kept.push(...rules.slice(index));
|
|
52
|
+
break;
|
|
53
|
+
}
|
|
54
|
+
// Compare against every other rule, but let only the earlier of two
|
|
55
|
+
// equivalent rules survive, or both would absorb each other.
|
|
56
|
+
const covering = rules.find((other, otherIndex) => otherIndex !== index &&
|
|
57
|
+
(otherIndex < index || !covers(rule, other, budget)) &&
|
|
58
|
+
covers(other, rule, budget));
|
|
59
|
+
if (covering) {
|
|
60
|
+
notes.push({
|
|
61
|
+
reason: `already covered by ${covering.source}`,
|
|
62
|
+
source: rule.source,
|
|
63
|
+
});
|
|
64
|
+
continue;
|
|
65
|
+
}
|
|
66
|
+
kept.push(absorbPatterns(rule, notes, budget));
|
|
67
|
+
}
|
|
68
|
+
return { notes, rules: kept };
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* The rules of an excluding set — `blacklist`, `disabledSites` — with
|
|
72
|
+
* everything that can never fire removed.
|
|
73
|
+
*
|
|
74
|
+
* A page has to pass every including set to reach the result at all, so an
|
|
75
|
+
* entry excluding pages none of them lets through excludes nothing. This is
|
|
76
|
+
* the bulk of what makes a generated query long: a config guards checkout,
|
|
77
|
+
* account and login while `enabledSites` only ever allowed the homepage and
|
|
78
|
+
* product pages. Configs write those guards as entries of a single rule, so
|
|
79
|
+
* the reduction works per entry and drops the rule only once a condition has
|
|
80
|
+
* no entry left that could match.
|
|
81
|
+
*
|
|
82
|
+
* @param reachable - The including sets, each a disjunction, all of which have
|
|
83
|
+
* to hold. An empty set means "everything" and is left out by the caller.
|
|
84
|
+
*/
|
|
85
|
+
export function dropUnreachableRules(rules, reachable, budget = UNLIMITED) {
|
|
86
|
+
const notes = [];
|
|
87
|
+
const kept = [];
|
|
88
|
+
const allowed = reachable.map((set) => ({
|
|
89
|
+
blocks: blockedSubjects(set),
|
|
90
|
+
rules: set,
|
|
91
|
+
source: sourceOf(set),
|
|
92
|
+
}));
|
|
93
|
+
for (const [index, rule] of rules.entries()) {
|
|
94
|
+
if (budget.exhausted()) {
|
|
95
|
+
notes.push(STOPPED_EARLY);
|
|
96
|
+
kept.push(...rules.slice(index));
|
|
97
|
+
break;
|
|
98
|
+
}
|
|
99
|
+
const narrowed = narrowToReachable(rule, allowed, notes, budget);
|
|
100
|
+
if (narrowed) {
|
|
101
|
+
kept.push(narrowed);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
return { notes, rules: kept };
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* Rules of an including set that an excluding rule covers completely — pages
|
|
108
|
+
* the config means to accelerate but never will.
|
|
109
|
+
*
|
|
110
|
+
* Unlike the reductions above this changes nothing about the query: it is a
|
|
111
|
+
* finding about the config, and the caller reports it.
|
|
112
|
+
*/
|
|
113
|
+
export function findShadowedRules(included, excluding, budget = UNLIMITED) {
|
|
114
|
+
const notes = [];
|
|
115
|
+
for (const rule of included) {
|
|
116
|
+
if (budget.exhausted()) {
|
|
117
|
+
break;
|
|
118
|
+
}
|
|
119
|
+
const shadowing = excluding.find((other) => covers(other, rule, budget));
|
|
120
|
+
if (shadowing) {
|
|
121
|
+
notes.push({
|
|
122
|
+
reason: `is completely excluded again by ${shadowing.source}, so it never applies`,
|
|
123
|
+
source: rule.source,
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
return notes;
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* The rule without the condition entries no reachable page can match, or
|
|
131
|
+
* `undefined` when a condition ends up with no entry at all — the rule then
|
|
132
|
+
* matches nothing.
|
|
133
|
+
*/
|
|
134
|
+
function narrowToReachable(rule, allowed, notes, budget) {
|
|
135
|
+
const conditions = [];
|
|
136
|
+
for (const condition of rule.conditions) {
|
|
137
|
+
const subject = resolvedSubject(condition.subject, rule.ruleSetVersion);
|
|
138
|
+
const reasons = new Map();
|
|
139
|
+
const patterns = condition.patterns.filter((pattern) => {
|
|
140
|
+
if (budget.exhausted()) {
|
|
141
|
+
return true;
|
|
142
|
+
}
|
|
143
|
+
const blocking = blockingSet(pattern, condition, rule, subject, allowed, budget);
|
|
144
|
+
if (blocking) {
|
|
145
|
+
reasons.set(pattern, blocking);
|
|
146
|
+
}
|
|
147
|
+
return !blocking;
|
|
148
|
+
});
|
|
149
|
+
if (patterns.length === 0) {
|
|
150
|
+
notes.push({
|
|
151
|
+
reason: `matches no page ${[...new Set(reasons.values())].join(" or ")} lets through`,
|
|
152
|
+
source: rule.source,
|
|
153
|
+
});
|
|
154
|
+
return undefined;
|
|
155
|
+
}
|
|
156
|
+
for (const [pattern, source] of reasons) {
|
|
157
|
+
notes.push({
|
|
158
|
+
reason: `${literalOf(pattern)} matches no page ${source} lets through`,
|
|
159
|
+
source: `${rule.source}.${condition.subject}`,
|
|
160
|
+
});
|
|
161
|
+
}
|
|
162
|
+
conditions.push({ ...condition, patterns });
|
|
163
|
+
}
|
|
164
|
+
return { ...rule, conditions };
|
|
165
|
+
}
|
|
166
|
+
/**
|
|
167
|
+
* The including set that keeps every page this entry matches out, if there is
|
|
168
|
+
* one. A set does that when none of its rules leaves room for the entry.
|
|
169
|
+
*/
|
|
170
|
+
function blockingSet(pattern, condition, rule, subject, allowed, budget) {
|
|
171
|
+
const candidates = allowed.filter((set) => set.blocks.has(subject));
|
|
172
|
+
if (candidates.length === 0) {
|
|
173
|
+
return undefined;
|
|
174
|
+
}
|
|
175
|
+
const language = languageOf(pattern, condition, rule, "atLeast");
|
|
176
|
+
if (!language) {
|
|
177
|
+
return undefined;
|
|
178
|
+
}
|
|
179
|
+
const blocking = candidates.find((set) => set.rules.every((allowedRule) => {
|
|
180
|
+
// A rule matching nothing keeps everything out.
|
|
181
|
+
if (allowedRule.matchesNothing) {
|
|
182
|
+
return true;
|
|
183
|
+
}
|
|
184
|
+
// A rule whose patterns have no automaton might leave room.
|
|
185
|
+
const allowedLanguage = languagesOf(allowedRule, "atLeast")?.get(subject);
|
|
186
|
+
return (!!allowedLanguage &&
|
|
187
|
+
budget.spend() &&
|
|
188
|
+
languagesDisjoint(language, allowedLanguage) === true);
|
|
189
|
+
}));
|
|
190
|
+
return blocking?.source;
|
|
191
|
+
}
|
|
192
|
+
/** The rule with the condition entries a sibling entry already covers removed. */
|
|
193
|
+
function absorbPatterns(rule, notes, budget) {
|
|
194
|
+
const conditions = rule.conditions.map((condition) => {
|
|
195
|
+
if (condition.patterns.length < 2) {
|
|
196
|
+
return condition;
|
|
197
|
+
}
|
|
198
|
+
// The same entry is offered as the covering one to every other entry, so
|
|
199
|
+
// its complement is built once.
|
|
200
|
+
const outsides = new Map();
|
|
201
|
+
const outsideFor = (pattern) => {
|
|
202
|
+
if (!outsides.has(pattern)) {
|
|
203
|
+
const language = languageOf(pattern, condition, rule, "atMost");
|
|
204
|
+
outsides.set(pattern, language && outsideOf(language));
|
|
205
|
+
}
|
|
206
|
+
return outsides.get(pattern);
|
|
207
|
+
};
|
|
208
|
+
const patterns = condition.patterns.filter((pattern, index) => {
|
|
209
|
+
if (budget.exhausted()) {
|
|
210
|
+
return true;
|
|
211
|
+
}
|
|
212
|
+
const covering = condition.patterns.find((other, otherIndex) => otherIndex === index
|
|
213
|
+
? false
|
|
214
|
+
: coversPattern(other, pattern, condition, rule, otherIndex < index, outsideFor, budget));
|
|
215
|
+
if (covering) {
|
|
216
|
+
notes.push({
|
|
217
|
+
reason: `${literalOf(pattern)} is already covered by ${literalOf(covering)}`,
|
|
218
|
+
source: `${rule.source}.${condition.subject}`,
|
|
219
|
+
});
|
|
220
|
+
}
|
|
221
|
+
return !covering;
|
|
222
|
+
});
|
|
223
|
+
return { ...condition, patterns };
|
|
224
|
+
});
|
|
225
|
+
return { ...rule, conditions };
|
|
226
|
+
}
|
|
227
|
+
/**
|
|
228
|
+
* True when every subject the entry matches is matched by `covering` too.
|
|
229
|
+
*
|
|
230
|
+
* @param mayAbsorbEquivalent - Whether `covering` is allowed to absorb an
|
|
231
|
+
* entry equivalent to it, which only the earlier of the two may do.
|
|
232
|
+
*/
|
|
233
|
+
function coversPattern(covering, pattern, condition, rule, mayAbsorbEquivalent, outsideFor, budget) {
|
|
234
|
+
if (!budget.spend()) {
|
|
235
|
+
return false;
|
|
236
|
+
}
|
|
237
|
+
const beyondCovering = outsideFor(covering);
|
|
238
|
+
const narrower = languageOf(pattern, condition, rule, "atLeast");
|
|
239
|
+
if (!beyondCovering ||
|
|
240
|
+
!narrower ||
|
|
241
|
+
isInside(narrower, beyondCovering) !== true) {
|
|
242
|
+
return false;
|
|
243
|
+
}
|
|
244
|
+
if (mayAbsorbEquivalent) {
|
|
245
|
+
return true;
|
|
246
|
+
}
|
|
247
|
+
const beyondPattern = outsideFor(pattern);
|
|
248
|
+
const coveringAtLeast = languageOf(covering, condition, rule, "atLeast");
|
|
249
|
+
const equivalent = !!beyondPattern &&
|
|
250
|
+
!!coveringAtLeast &&
|
|
251
|
+
isInside(coveringAtLeast, beyondPattern) === true;
|
|
252
|
+
return !equivalent;
|
|
253
|
+
}
|
|
254
|
+
/** The language of a single condition entry. */
|
|
255
|
+
function languageOf(pattern, condition, rule, bound) {
|
|
256
|
+
return conditionLanguage({ patterns: [pattern], subject: condition.subject }, rule.ruleSetVersion, bound);
|
|
257
|
+
}
|
|
258
|
+
/** True when every page `rule` matches is matched by `covering` as well. */
|
|
259
|
+
function covers(covering, rule, budget) {
|
|
260
|
+
if (covering.matchesNothing || rule.matchesNothing) {
|
|
261
|
+
return false;
|
|
262
|
+
}
|
|
263
|
+
if (!coversContentTypes(covering, rule)) {
|
|
264
|
+
return false;
|
|
265
|
+
}
|
|
266
|
+
// A subject the covering rule constrains and the other one does not leaves
|
|
267
|
+
// that one wider. Checking the subjects first keeps the automata out of the
|
|
268
|
+
// comparison of two rules that talk about different parts of the URL.
|
|
269
|
+
const constrained = subjectsOf(rule);
|
|
270
|
+
if (![...subjectsOf(covering)].every((subject) => constrained.has(subject))) {
|
|
271
|
+
return false;
|
|
272
|
+
}
|
|
273
|
+
// Everything past here builds automata, so it costs a comparison.
|
|
274
|
+
if (!budget.spend()) {
|
|
275
|
+
return false;
|
|
276
|
+
}
|
|
277
|
+
// Containment holds when everything the rule can match — at least — stays
|
|
278
|
+
// inside what the covering rule matches at most.
|
|
279
|
+
const languages = languagesOf(rule, "atLeast");
|
|
280
|
+
const outside = outsideLanguagesOf(covering);
|
|
281
|
+
if (!languages || !outside) {
|
|
282
|
+
return false;
|
|
283
|
+
}
|
|
284
|
+
// Every subject the covering rule constrains has to be constrained at least
|
|
285
|
+
// as narrowly by the other one. A subject it does not constrain is free,
|
|
286
|
+
// which can only make it wider.
|
|
287
|
+
for (const [subject, beyond] of outside) {
|
|
288
|
+
const narrower = languages.get(subject);
|
|
289
|
+
if (!narrower || !beyond || isInside(narrower, beyond) !== true) {
|
|
290
|
+
return false;
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
return true;
|
|
294
|
+
}
|
|
295
|
+
/**
|
|
296
|
+
* The language of every subject a rule constrains, or `undefined` when one of
|
|
297
|
+
* them has no automaton — in which case the rule takes part in no reduction.
|
|
298
|
+
*/
|
|
299
|
+
function languagesOf(rule, bound) {
|
|
300
|
+
const cached = ruleCache.get(rule);
|
|
301
|
+
if (cached?.has(bound)) {
|
|
302
|
+
return cached.get(bound);
|
|
303
|
+
}
|
|
304
|
+
const languages = buildLanguagesOf(rule, bound);
|
|
305
|
+
if (cached) {
|
|
306
|
+
cached.set(bound, languages);
|
|
307
|
+
}
|
|
308
|
+
else {
|
|
309
|
+
ruleCache.set(rule, new Map([[bound, languages]]));
|
|
310
|
+
}
|
|
311
|
+
return languages;
|
|
312
|
+
}
|
|
313
|
+
/** Languages already collected, per rule. */
|
|
314
|
+
const ruleCache = new WeakMap();
|
|
315
|
+
/** Complements already built, per rule. */
|
|
316
|
+
const outsideCache = new WeakMap();
|
|
317
|
+
/**
|
|
318
|
+
* What every subject of a rule does *not* match, built once per rule: the same
|
|
319
|
+
* rule is offered as the covering one to every other rule of its set.
|
|
320
|
+
*/
|
|
321
|
+
function outsideLanguagesOf(rule) {
|
|
322
|
+
if (outsideCache.has(rule)) {
|
|
323
|
+
return outsideCache.get(rule);
|
|
324
|
+
}
|
|
325
|
+
const languages = languagesOf(rule, "atMost");
|
|
326
|
+
const outside = languages
|
|
327
|
+
? new Map([...languages].map(([subject, language]) => [
|
|
328
|
+
subject,
|
|
329
|
+
outsideOf(language),
|
|
330
|
+
]))
|
|
331
|
+
: undefined;
|
|
332
|
+
outsideCache.set(rule, outside);
|
|
333
|
+
return outside;
|
|
334
|
+
}
|
|
335
|
+
function buildLanguagesOf(rule, bound) {
|
|
336
|
+
const languages = new Map();
|
|
337
|
+
for (const condition of rule.conditions) {
|
|
338
|
+
const language = conditionLanguage(condition, rule.ruleSetVersion, bound);
|
|
339
|
+
if (!language) {
|
|
340
|
+
return undefined;
|
|
341
|
+
}
|
|
342
|
+
languages.set(resolvedSubject(condition.subject, rule.ruleSetVersion), language);
|
|
343
|
+
}
|
|
344
|
+
return languages;
|
|
345
|
+
}
|
|
346
|
+
/** True when `covering` applies wherever `rule` does, content-type-wise. */
|
|
347
|
+
function coversContentTypes(covering, rule) {
|
|
348
|
+
if (!covering.contentTypes) {
|
|
349
|
+
return true;
|
|
350
|
+
}
|
|
351
|
+
return (rule.contentTypes?.every((type) => covering.contentTypes?.includes(type)) ??
|
|
352
|
+
false);
|
|
353
|
+
}
|
|
354
|
+
/** The subjects a rule constrains. */
|
|
355
|
+
function subjectsOf(rule) {
|
|
356
|
+
return new Set(rule.conditions.map(({ subject }) => resolvedSubject(subject, rule.ruleSetVersion)));
|
|
357
|
+
}
|
|
358
|
+
/**
|
|
359
|
+
* The subjects on which a set of allowed rules can keep something out: those
|
|
360
|
+
* every one of its rules constrains.
|
|
361
|
+
*/
|
|
362
|
+
function blockedSubjects(rules) {
|
|
363
|
+
let blocked = new Set(SUBJECTS);
|
|
364
|
+
for (const rule of rules) {
|
|
365
|
+
// A rule matching nothing keeps everything out, on every subject.
|
|
366
|
+
if (rule.matchesNothing) {
|
|
367
|
+
continue;
|
|
368
|
+
}
|
|
369
|
+
const constrained = subjectsOf(rule);
|
|
370
|
+
blocked = new Set([...blocked].filter((subject) => constrained.has(subject)));
|
|
371
|
+
}
|
|
372
|
+
return blocked;
|
|
373
|
+
}
|
|
374
|
+
/** The rule set a group of rules came from, for a readable reason. */
|
|
375
|
+
function sourceOf(rules) {
|
|
376
|
+
const [first] = rules;
|
|
377
|
+
return first ? first.source.replace(/\[\d+].*$/, "") : "the enabled pages";
|
|
378
|
+
}
|
|
379
|
+
function literalOf(pattern) {
|
|
380
|
+
return pattern.kind === "regexp"
|
|
381
|
+
? String(pattern.value)
|
|
382
|
+
: `"${pattern.value}"`;
|
|
383
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { NormalizedRule } from "./rule-model.js";
|
|
2
|
+
export interface RelevanceContext {
|
|
3
|
+
/**
|
|
4
|
+
* Content types a row of the queried relation can have. A page impression
|
|
5
|
+
* is a document, so a rule written for images or scripts can never apply.
|
|
6
|
+
*/
|
|
7
|
+
readonly contentTypes?: readonly string[];
|
|
8
|
+
/**
|
|
9
|
+
* Hosts the query is already restricted to — the app's origins. A rule
|
|
10
|
+
* whose host condition matches none of them can never fire. Leave empty
|
|
11
|
+
* when the hosts are not known, and the check is skipped.
|
|
12
|
+
*/
|
|
13
|
+
readonly hosts?: readonly string[];
|
|
14
|
+
}
|
|
15
|
+
export interface RuleNote {
|
|
16
|
+
readonly reason: string;
|
|
17
|
+
/** Where the rule sits in the config, e.g. `blacklist[3]`. */
|
|
18
|
+
readonly source: string;
|
|
19
|
+
}
|
|
20
|
+
export interface RelevanceResult {
|
|
21
|
+
/**
|
|
22
|
+
* Rules that survive but do not mean exactly what the config means — they
|
|
23
|
+
* carry a criterion outside the URL that the query cannot express, so the
|
|
24
|
+
* emitted filter is wider than the rule.
|
|
25
|
+
*/
|
|
26
|
+
readonly approximated: RuleNote[];
|
|
27
|
+
/** Rules that can never apply to a row of this query. */
|
|
28
|
+
readonly dropped: RuleNote[];
|
|
29
|
+
readonly rules: NormalizedRule[];
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Rules the query cannot possibly be affected by, removed.
|
|
33
|
+
*
|
|
34
|
+
* This is the first half of ruling the rule sets against each other: not rule
|
|
35
|
+
* against rule yet, but every rule against the frame the query already sets —
|
|
36
|
+
* documents, on this app's origins. A `contentType: ['image']` blacklist entry
|
|
37
|
+
* or a rule for a third-party host says nothing about a page impression, and
|
|
38
|
+
* keeping it only narrows the result for no reason.
|
|
39
|
+
*
|
|
40
|
+
* Nothing is dropped silently: every decision comes back in `dropped` or
|
|
41
|
+
* `approximated` so it can be reported.
|
|
42
|
+
*/
|
|
43
|
+
export declare function keepRelevantRules(rules: readonly NormalizedRule[], context?: RelevanceContext): RelevanceResult;
|