@klnap/next-proxy-chain 1.0.0 → 1.0.2

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/dist/index.cjs CHANGED
@@ -32,8 +32,10 @@ __export(index_exports, {
32
32
  createProxyContext: () => createProxyContext,
33
33
  decodeOverriddenRequestHeaders: () => decodeOverriddenRequestHeaders,
34
34
  defineProxy: () => defineProxy,
35
+ finalizeResponse: () => finalizeResponse,
35
36
  formatPipelineLine: () => formatPipelineLine,
36
37
  headersDiffer: () => headersDiffer,
38
+ headersInitToHeaders: () => headersInitToHeaders,
37
39
  host: () => host,
38
40
  isNextResult: () => isNextResult,
39
41
  isRequestHeaderAllowed: () => isRequestHeaderAllowed,
@@ -46,6 +48,7 @@ __export(index_exports, {
46
48
  path: () => path,
47
49
  pathMatches: () => pathMatches,
48
50
  proxyChain: () => proxyChain,
51
+ resolveRuleResult: () => resolveRuleResult,
49
52
  responseHeaderItems: () => responseHeaderItems,
50
53
  ruleLabel: () => ruleLabel,
51
54
  ruleMatches: () => ruleMatches,
@@ -57,22 +60,39 @@ __export(index_exports, {
57
60
  withRuleTimeout: () => withRuleTimeout
58
61
  });
59
62
  module.exports = __toCommonJS(index_exports);
60
- var import_server = require("next/server");
61
63
 
62
- // src/core.ts
64
+ // src/core/constants.ts
63
65
  var NEXT = /* @__PURE__ */ Symbol.for("proxy-chain.next");
64
- function next(options = {}) {
65
- return { [NEXT]: true, ...options };
66
- }
67
- function isNextResult(value) {
68
- return typeof value === "object" && value !== null && NEXT in value;
69
- }
66
+ var DEFAULT_BLOCKED_REQUEST_HEADERS = Object.freeze([
67
+ "host",
68
+ "connection",
69
+ "keep-alive",
70
+ "proxy-authenticate",
71
+ "proxy-authorization",
72
+ "te",
73
+ "trailer",
74
+ "transfer-encoding",
75
+ "upgrade",
76
+ "content-length",
77
+ "cookie",
78
+ "authorization",
79
+ "x-forwarded-host",
80
+ "x-forwarded-for",
81
+ "x-forwarded-proto",
82
+ "x-real-ip",
83
+ "x-middleware-next",
84
+ "x-middleware-override-headers"
85
+ ]);
86
+
87
+ // src/core/context.ts
70
88
  function createProxyContext() {
71
89
  return /* @__PURE__ */ new Map();
72
90
  }
73
- var USELESS_FN_NAMES = /* @__PURE__ */ new Set(["", "proxy", "anonymous", "run", "next"]);
74
- function usefulName(value) {
75
- if (!value || USELESS_FN_NAMES.has(value)) return void 0;
91
+
92
+ // src/core/matching.ts
93
+ var RESERVED_FN_NAMES = /* @__PURE__ */ new Set(["", "proxy", "anonymous", "run", "next"]);
94
+ function extractMeaningfulName(value) {
95
+ if (!value || RESERVED_FN_NAMES.has(value)) return void 0;
76
96
  return value;
77
97
  }
78
98
  function toStatelessRegExp(re) {
@@ -93,6 +113,39 @@ function host(matcher) {
93
113
  function isScopeObject(value) {
94
114
  return "host" in value || "path" in value;
95
115
  }
116
+ function segmentToRegex(segment) {
117
+ if (segment === "**") return "(?:.*)";
118
+ if (segment === "*") return "[^/]+";
119
+ if (segment.startsWith(":")) return "[^/]+";
120
+ return escapeRegex(segment);
121
+ }
122
+ function path(pattern, options = {}) {
123
+ const { locales = [] } = options;
124
+ const localePrefix = locales.length > 0 ? `(?:(?:${locales.map(escapeRegex).join("|")})\\/)?` : "";
125
+ if (pattern instanceof RegExp) {
126
+ const body2 = pattern.source.replace(/^\^/, "").replace(/\$$/, "");
127
+ const needsStart = !pattern.source.startsWith("^");
128
+ const needsEnd = !pattern.source.endsWith("$");
129
+ return toStatelessRegExp(
130
+ new RegExp(
131
+ `${needsStart ? "^" : ""}${localePrefix ? `/${localePrefix}` : ""}${body2}${needsEnd ? "$" : ""}`,
132
+ pattern.flags
133
+ )
134
+ );
135
+ }
136
+ const normalized = (pattern.startsWith("/") ? pattern : `/${pattern}`).replace(/\/$/, "") || "/";
137
+ const segments = normalized.slice(1).split("/");
138
+ const trailingStar = segments.length > 0 && segments[segments.length - 1] === "*";
139
+ const suffix = trailingStar ? "(?:\\/.*)?$" : "$";
140
+ const body = trailingStar ? segments.slice(0, -1).map(segmentToRegex).concat("[^/]+").join("\\/") : segments.map(segmentToRegex).join("\\/");
141
+ return new RegExp(`^/${localePrefix}${body}${suffix}`);
142
+ }
143
+ function toPathRegex(matcher, options) {
144
+ const list = Array.isArray(matcher) ? matcher : [matcher];
145
+ const regexes = list.map((m) => path(m, options));
146
+ if (regexes.length === 1) return toStatelessRegExp(regexes[0]);
147
+ return toStatelessRegExp(new RegExp(`(?:${regexes.map((r) => `(?:${r.source})`).join("|")})`));
148
+ }
96
149
  function compileScopes(input, pathOptions) {
97
150
  if (input == null) return void 0;
98
151
  const list = Array.isArray(input) ? input : [input];
@@ -137,12 +190,12 @@ function scopeMatches(pathname, hostname, scope) {
137
190
  }
138
191
  function toRule(entry) {
139
192
  if (typeof entry === "function") {
140
- const name2 = usefulName(entry.name);
193
+ const name2 = extractMeaningfulName(entry.name);
141
194
  return name2 ? { run: entry, name: name2 } : { run: entry };
142
195
  }
143
196
  const rule = entry;
144
- const runName = typeof rule.run === "function" ? usefulName(rule.run.name) : void 0;
145
- const name = usefulName(rule.name) ?? runName;
197
+ const runName = typeof rule.run === "function" ? extractMeaningfulName(rule.run.name) : void 0;
198
+ const name = extractMeaningfulName(rule.name) ?? runName;
146
199
  const harden = (scopes) => scopes?.map((s) => ({
147
200
  host: s.host ? toStatelessRegExp(s.host) : void 0,
148
201
  path: s.path ? toStatelessRegExp(s.path) : void 0
@@ -155,8 +208,8 @@ function toRule(entry) {
155
208
  };
156
209
  }
157
210
  function ruleLabel(rule, index) {
158
- const runName = typeof rule.run === "function" ? usefulName(rule.run.name) : void 0;
159
- return usefulName(rule.name) ?? runName ?? `rule#${index}`;
211
+ const runName = typeof rule.run === "function" ? extractMeaningfulName(rule.run.name) : void 0;
212
+ return extractMeaningfulName(rule.name) ?? runName ?? `rule#${index}`;
160
213
  }
161
214
  function withPaths(entry, filter) {
162
215
  const base = toRule(entry);
@@ -188,6 +241,58 @@ function normalizeHost(raw) {
188
241
  const withoutPort = first.replace(/:\d+$/, "");
189
242
  return withoutPort || null;
190
243
  }
244
+ function scopeKey(scope) {
245
+ return `${scope.host?.source ?? "*"}::${scope.host?.flags ?? ""}|${scope.path?.source ?? "*"}::${scope.path?.flags ?? ""}`;
246
+ }
247
+ function assertIncludeExcludeNoOverlap(include, exclude, label) {
248
+ if (!include?.length || !exclude?.length) return;
249
+ const excluded = new Set(exclude.map(scopeKey));
250
+ for (const scope of include) {
251
+ if (excluded.has(scopeKey(scope))) {
252
+ throw new Error(
253
+ `[proxy-chain] ${label}: include and exclude share an identical scope. Remove one.`
254
+ );
255
+ }
256
+ }
257
+ }
258
+
259
+ // src/core/headers.ts
260
+ function normalizeHeaderName(name) {
261
+ return name.toLowerCase();
262
+ }
263
+ function headersInitToHeaders(init) {
264
+ return new Headers(init);
265
+ }
266
+ function headersDiffer(base, overrides) {
267
+ for (const [key, value] of overrides.entries()) {
268
+ if (base.get(key) !== value) return true;
269
+ }
270
+ return false;
271
+ }
272
+ function isRequestHeaderAllowed(name, policy = {}) {
273
+ const key = normalizeHeaderName(name);
274
+ const allowed = new Set((policy.allowed ?? []).map(normalizeHeaderName));
275
+ if (allowed.has(key)) return true;
276
+ if (key.startsWith("x-middleware-")) return false;
277
+ const blocked = /* @__PURE__ */ new Set([
278
+ ...DEFAULT_BLOCKED_REQUEST_HEADERS.map(normalizeHeaderName),
279
+ ...(policy.blocked ?? []).map(normalizeHeaderName)
280
+ ]);
281
+ return !blocked.has(key);
282
+ }
283
+ function mergeRequestHeaderOverrides(target, overrides, policy = {}) {
284
+ const dropped = [];
285
+ let applied = 0;
286
+ overrides.forEach((value, key) => {
287
+ if (!isRequestHeaderAllowed(key, policy)) {
288
+ dropped.push(key);
289
+ return;
290
+ }
291
+ target.set(key, value);
292
+ applied++;
293
+ });
294
+ return { applied, dropped };
295
+ }
191
296
  function parseCookieName(setCookie) {
192
297
  const eq = setCookie.indexOf("=");
193
298
  return (eq === -1 ? setCookie : setCookie.slice(0, eq)).trim();
@@ -245,7 +350,7 @@ function mergeWrites(writes, strategy, onConflict) {
245
350
  const previousRule = setBy.get(key);
246
351
  if (typeof strategy === "function") {
247
352
  const result = strategy(key, existing, value);
248
- if (result === null) resolved.delete(key);
353
+ if (result === null || result === void 0) resolved.delete(key);
249
354
  else resolved.set(key, result);
250
355
  setBy.set(key, write.ruleName);
251
356
  continue;
@@ -270,109 +375,16 @@ function applyMergedToResponse(res, headers, cookies) {
270
375
  res.headers.append("set-cookie", cookie);
271
376
  }
272
377
  }
273
- var DEFAULT_BLOCKED_REQUEST_HEADERS = [
274
- "host",
275
- "connection",
276
- "keep-alive",
277
- "proxy-authenticate",
278
- "proxy-authorization",
279
- "te",
280
- "trailer",
281
- "transfer-encoding",
282
- "upgrade",
283
- "content-length",
284
- "cookie",
285
- "authorization",
286
- "x-forwarded-host",
287
- "x-forwarded-for",
288
- "x-forwarded-proto",
289
- "x-real-ip",
290
- "x-middleware-next",
291
- "x-middleware-override-headers"
292
- ];
293
- function normalizeHeaderName(name) {
294
- return name.toLowerCase();
295
- }
296
- function isRequestHeaderAllowed(name, policy = {}) {
297
- const key = normalizeHeaderName(name);
298
- const allowed = new Set((policy.allowed ?? []).map(normalizeHeaderName));
299
- if (allowed.has(key)) return true;
300
- if (key.startsWith("x-middleware-")) return false;
301
- const blocked = /* @__PURE__ */ new Set([
302
- ...DEFAULT_BLOCKED_REQUEST_HEADERS.map(normalizeHeaderName),
303
- ...(policy.blocked ?? []).map(normalizeHeaderName)
304
- ]);
305
- return !blocked.has(key);
306
- }
307
- function mergeRequestHeaderOverrides(target, overrides, policy = {}) {
308
- const dropped = [];
309
- let applied = 0;
310
- overrides.forEach((value, key) => {
311
- if (!isRequestHeaderAllowed(key, policy)) {
312
- dropped.push(key);
313
- return;
314
- }
315
- target.set(key, value);
316
- applied++;
317
- });
318
- return { applied, dropped };
319
- }
320
- function headersInitToHeaders(init) {
321
- return new Headers(init);
322
- }
323
- function headersDiffer(base, overrides) {
324
- for (const [key, value] of overrides.entries()) {
325
- if (base.get(key) !== value) return true;
326
- }
327
- return false;
328
- }
329
- function segmentToRegex(segment) {
330
- if (segment === "**") return "(?:.*)";
331
- if (segment === "*") return "[^/]+";
332
- if (segment.startsWith(":")) return "[^/]+";
333
- return escapeRegex(segment);
334
- }
335
- function path(pattern, options = {}) {
336
- const { locales = [] } = options;
337
- const localePrefix = locales.length > 0 ? `(?:(?:${locales.map(escapeRegex).join("|")})\\/)?` : "";
338
- if (pattern instanceof RegExp) {
339
- const body2 = pattern.source.replace(/^\^/, "").replace(/\$$/, "");
340
- const needsStart = !pattern.source.startsWith("^");
341
- const needsEnd = !pattern.source.endsWith("$");
342
- return toStatelessRegExp(
343
- new RegExp(
344
- `${needsStart ? "^" : ""}${localePrefix ? `/${localePrefix}` : ""}${body2}${needsEnd ? "$" : ""}`,
345
- pattern.flags
346
- )
347
- );
348
- }
349
- const normalized = (pattern.startsWith("/") ? pattern : `/${pattern}`).replace(/\/$/, "") || "/";
350
- const segments = normalized.slice(1).split("/");
351
- const trailingStar = segments.length > 0 && segments[segments.length - 1] === "*";
352
- const suffix = trailingStar ? "(?:\\/.*)?$" : "$";
353
- const body = trailingStar ? segments.slice(0, -1).map(segmentToRegex).concat("[^/]+").join("\\/") : segments.map(segmentToRegex).join("\\/");
354
- return new RegExp(`^/${localePrefix}${body}${suffix}`);
355
- }
356
- function toPathRegex(matcher, options) {
357
- const list = Array.isArray(matcher) ? matcher : [matcher];
358
- const regexes = list.map((m) => path(m, options));
359
- if (regexes.length === 1) return toStatelessRegExp(regexes[0]);
360
- return toStatelessRegExp(new RegExp(`(?:${regexes.map((r) => `(?:${r.source})`).join("|")})`));
361
- }
362
- function scopeKey(scope) {
363
- return `${scope.host?.source ?? "*"}::${scope.host?.flags ?? ""}|${scope.path?.source ?? "*"}::${scope.path?.flags ?? ""}`;
378
+
379
+ // src/core/actions.ts
380
+ function next(options = {}) {
381
+ return { [NEXT]: true, ...options };
364
382
  }
365
- function assertIncludeExcludeNoOverlap(include, exclude, label) {
366
- if (!include?.length || !exclude?.length) return;
367
- const excluded = new Set(exclude.map(scopeKey));
368
- for (const scope of include) {
369
- if (excluded.has(scopeKey(scope))) {
370
- throw new Error(
371
- `[proxy-chain] ${label}: include and exclude share an identical scope. Remove one.`
372
- );
373
- }
374
- }
383
+ function isNextResult(value) {
384
+ return typeof value === "object" && value !== null && NEXT in value;
375
385
  }
386
+
387
+ // src/core/logger.ts
376
388
  var LEVEL_RANK = {
377
389
  debug: 10,
378
390
  info: 20,
@@ -414,6 +426,8 @@ function formatPipelineLine(input) {
414
426
  return `[proxy-chain] \xD7 ${req} | ${name} (${formatDuration(outcome.duration)}) -> stop ${outcome.status}`;
415
427
  }
416
428
  }
429
+
430
+ // src/core/timeout.ts
417
431
  var RuleTimeoutError = class extends Error {
418
432
  ruleName;
419
433
  timeoutMs;
@@ -437,45 +451,8 @@ async function withRuleTimeout(promise, timeoutMs, ruleName) {
437
451
  }
438
452
  }
439
453
 
440
- // src/index.ts
441
- function isPathFilter(value) {
442
- if (value === null || typeof value !== "object") return false;
443
- if ("nextUrl" in value) return false;
444
- return "include" in value || "exclude" in value || "name" in value || "pathOptions" in value;
445
- }
446
- function defineProxy(nameOrRun, maybeRun) {
447
- const defaultName = typeof nameOrRun === "string" ? nameOrRun : void 0;
448
- const run = typeof nameOrRun === "function" ? nameOrRun : maybeRun;
449
- const proxy = ((reqOrFilter, eventOrCtx, maybeEvent) => {
450
- if (isPathFilter(reqOrFilter)) {
451
- return withPaths(run, {
452
- ...reqOrFilter,
453
- name: reqOrFilter.name ?? defaultName
454
- });
455
- }
456
- return run(reqOrFilter, eventOrCtx, maybeEvent);
457
- });
458
- Object.defineProperty(proxy, "name", {
459
- value: defaultName ?? "",
460
- configurable: true
461
- });
462
- return proxy;
463
- }
464
- function resolveConfig(overrides = {}) {
465
- const { logger, debug, ...rest } = overrides;
466
- return {
467
- createContext: () => createProxyContext(),
468
- cookieMergeStrategy: "last-write-wins",
469
- headerMergeStrategy: "last-write-wins",
470
- debug: false,
471
- ruleTimeoutMs: 0,
472
- requestHeaderPolicy: {},
473
- strictOverrideCheck: false,
474
- ...rest,
475
- ...debug !== void 0 ? { debug } : {},
476
- logger: logger ?? createConsoleLogger(debug ? "debug" : "info")
477
- };
478
- }
454
+ // src/core/pipeline.ts
455
+ var import_server = require("next/server");
479
456
  function safeInternalError() {
480
457
  return new import_server.NextResponse("Internal Server Error", {
481
458
  status: 500,
@@ -564,6 +541,57 @@ function resolveRuleResult(res, requestHeaderPolicy, logger, debug) {
564
541
  }
565
542
  return { kind: "stop", response: res };
566
543
  }
544
+ function finalizeResponse(res, headerWrites, cookieWrites, headerStrategy, cookieStrategy, logger, debug) {
545
+ const headers = mergeWrites(headerWrites, headerStrategy, (key, winner, loser) => {
546
+ if (debug) logger.debug({ header: key, winner, loser }, "header conflict resolved");
547
+ });
548
+ const cookies = mergeWrites(cookieWrites, cookieStrategy, (key, winner, loser) => {
549
+ if (debug) logger.debug({ cookie: key, winner, loser }, "cookie conflict resolved");
550
+ });
551
+ applyMergedToResponse(res, headers, cookies);
552
+ return res;
553
+ }
554
+
555
+ // src/core/engine.ts
556
+ var import_server2 = require("next/server");
557
+ function isPathFilter(value) {
558
+ if (value === null || typeof value !== "object") return false;
559
+ if ("nextUrl" in value) return false;
560
+ return "include" in value || "exclude" in value || "name" in value || "pathOptions" in value;
561
+ }
562
+ function defineProxy(nameOrRun, maybeRun) {
563
+ const defaultName = typeof nameOrRun === "string" ? nameOrRun : void 0;
564
+ const run = typeof nameOrRun === "function" ? nameOrRun : maybeRun;
565
+ const proxy = ((reqOrFilter, eventOrCtx, maybeEvent) => {
566
+ if (isPathFilter(reqOrFilter)) {
567
+ return withPaths(run, {
568
+ ...reqOrFilter,
569
+ name: reqOrFilter.name ?? defaultName
570
+ });
571
+ }
572
+ return run(reqOrFilter, eventOrCtx, maybeEvent);
573
+ });
574
+ Object.defineProperty(proxy, "name", {
575
+ value: defaultName ?? "",
576
+ configurable: true
577
+ });
578
+ return proxy;
579
+ }
580
+ function resolveConfig(overrides = {}) {
581
+ const { logger, debug, ...rest } = overrides;
582
+ return {
583
+ createContext: () => createProxyContext(),
584
+ cookieMergeStrategy: "last-write-wins",
585
+ headerMergeStrategy: "last-write-wins",
586
+ debug: false,
587
+ ruleTimeoutMs: 0,
588
+ requestHeaderPolicy: {},
589
+ strictOverrideCheck: false,
590
+ ...rest,
591
+ ...debug !== void 0 ? { debug } : {},
592
+ logger: logger ?? createConsoleLogger(debug ? "debug" : "info")
593
+ };
594
+ }
567
595
  function proxyChain(optionsOrEntries, maybeEntries) {
568
596
  const options = Array.isArray(optionsOrEntries) ? {} : optionsOrEntries;
569
597
  const entries = Array.isArray(optionsOrEntries) ? optionsOrEntries : maybeEntries;
@@ -609,14 +637,15 @@ function proxyChain(optionsOrEntries, maybeEntries) {
609
637
  merged.set(k, v);
610
638
  });
611
639
  pendingRequestOverrides = null;
612
- currentReq = new import_server.NextRequest(currentReq, { headers: merged });
640
+ currentReq = new import_server2.NextRequest(currentReq, { headers: merged });
613
641
  return currentReq;
614
642
  };
615
643
  const appliedRequestOverrides = new Headers();
616
644
  const queueRequestOverrides = (overrides) => {
617
645
  if (!pendingRequestOverrides) pendingRequestOverrides = new Headers();
618
- mergeRequestHeaderOverrides(pendingRequestOverrides, overrides, requestHeaderPolicy);
646
+ const target = pendingRequestOverrides;
619
647
  overrides.forEach((v, k) => {
648
+ target.set(k, v);
620
649
  appliedRequestOverrides.set(k, v);
621
650
  });
622
651
  };
@@ -670,7 +699,7 @@ function proxyChain(optionsOrEntries, maybeEntries) {
670
699
  })
671
700
  );
672
701
  }
673
- return finalize(
702
+ return finalizeResponse(
674
703
  recovered,
675
704
  headerWrites,
676
705
  cookieWrites,
@@ -767,7 +796,7 @@ function proxyChain(optionsOrEntries, maybeEntries) {
767
796
  }
768
797
  }
769
798
  }
770
- return finalize(
799
+ return finalizeResponse(
771
800
  outcome.response,
772
801
  headerWrites,
773
802
  cookieWrites,
@@ -806,11 +835,11 @@ function proxyChain(optionsOrEntries, maybeEntries) {
806
835
  let final;
807
836
  if (finalRewriteUrl) {
808
837
  const rewriteTarget = new URL(finalRewriteUrl, finalReq.url);
809
- final = import_server.NextResponse.rewrite(rewriteTarget, { request: { headers: finalReq.headers } });
838
+ final = import_server2.NextResponse.rewrite(rewriteTarget, { request: { headers: finalReq.headers } });
810
839
  } else {
811
- final = import_server.NextResponse.next({ request: { headers: finalReq.headers } });
840
+ final = import_server2.NextResponse.next({ request: { headers: finalReq.headers } });
812
841
  }
813
- return finalize(
842
+ return finalizeResponse(
814
843
  final,
815
844
  headerWrites,
816
845
  cookieWrites,
@@ -832,16 +861,6 @@ function proxyChain(optionsOrEntries, maybeEntries) {
832
861
  }
833
862
  };
834
863
  }
835
- function finalize(res, headerWrites, cookieWrites, headerStrategy, cookieStrategy, logger, debug) {
836
- const headers = mergeWrites(headerWrites, headerStrategy, (key, winner, loser) => {
837
- if (debug) logger.debug({ header: key, winner, loser }, "header conflict resolved");
838
- });
839
- const cookies = mergeWrites(cookieWrites, cookieStrategy, (key, winner, loser) => {
840
- if (debug) logger.debug({ cookie: key, winner, loser }, "cookie conflict resolved");
841
- });
842
- applyMergedToResponse(res, headers, cookies);
843
- return res;
844
- }
845
864
  // Annotate the CommonJS export names for ESM import in node:
846
865
  0 && (module.exports = {
847
866
  DEFAULT_BLOCKED_REQUEST_HEADERS,
@@ -856,8 +875,10 @@ function finalize(res, headerWrites, cookieWrites, headerStrategy, cookieStrateg
856
875
  createProxyContext,
857
876
  decodeOverriddenRequestHeaders,
858
877
  defineProxy,
878
+ finalizeResponse,
859
879
  formatPipelineLine,
860
880
  headersDiffer,
881
+ headersInitToHeaders,
861
882
  host,
862
883
  isNextResult,
863
884
  isRequestHeaderAllowed,
@@ -870,6 +891,7 @@ function finalize(res, headerWrites, cookieWrites, headerStrategy, cookieStrateg
870
891
  path,
871
892
  pathMatches,
872
893
  proxyChain,
894
+ resolveRuleResult,
873
895
  responseHeaderItems,
874
896
  ruleLabel,
875
897
  ruleMatches,