@flesh-and-blood/search 5.0.15 → 5.0.16

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.
@@ -55,6 +55,15 @@ export declare const FilterKind: {
55
55
  export type FilterKind = (typeof FilterKind)[keyof typeof FilterKind];
56
56
  export type Modifier = ">=" | ">" | "<=" | "<";
57
57
  export declare const availableModifiers: Modifier[];
58
+ /**
59
+ * A value written against a filter, carrying the comparison written in front of
60
+ * it. Each value takes its own, so one term can ask for a cost of 1 or a cost
61
+ * above 2.
62
+ */
63
+ export interface FilterValue {
64
+ modifier?: Modifier;
65
+ value: string;
66
+ }
58
67
  export type Exclusion = "!" | "-";
59
68
  export declare const availableExclusions: Exclusion[];
60
69
  /** The card field a mapping reads. */
@@ -62,7 +71,7 @@ export type CardPropertyName = keyof Card;
62
71
  /**
63
72
  * The property of a mapping that reads no card field: a meta filter expands
64
73
  * into other filters before any card is read, and a relation filter matches
65
- * the identifiers the parser resolved.
74
+ * the identifiers the parse resolved.
66
75
  */
67
76
  export declare const NO_CARD_PROPERTY = "n/a";
68
77
  /**
@@ -71,7 +80,7 @@ export declare const NO_CARD_PROPERTY = "n/a";
71
80
  */
72
81
  export type CardSpecialPropertyName = "specialArcane" | "specialCost" | "specialDefense" | "specialLife" | "specialPower";
73
82
  /**
74
- * How a card property is read and compared. The parser builds some of these
83
+ * How a card property is read and compared. The parse builds some of these
75
84
  * for itself, expanding a meta filter or matching the cards a relation filter
76
85
  * resolved, and those name no filter key, so they carry no grammar.
77
86
  */
@@ -103,6 +112,38 @@ export interface CardPropertyMapping {
103
112
  partialMatch?: boolean;
104
113
  specialProperty?: CardSpecialPropertyName;
105
114
  }
115
+ /**
116
+ * A filter a query asked for: the mapping to match cards against, the values
117
+ * to match, and how they combine. The parse builds these; the matcher reads
118
+ * them.
119
+ */
120
+ export interface AppliedFilter {
121
+ filterToPropertyMapping: CardPropertyMapping;
122
+ values: string[];
123
+ /**
124
+ * The same values with the comparison each was written behind, for the
125
+ * filters whose values a query writes directly. Absent where the parse
126
+ * resolved a value into others (a format into its heroes, a rarity into the
127
+ * ones ranked above it), which no comparison reaches.
128
+ */
129
+ filterValues?: FilterValue[];
130
+ /**
131
+ * The same strings as `values`, for filters whose match is exact membership
132
+ * rather than a comparison, so a card costs one lookup instead of a scan.
133
+ * Consumers read `values`; this is the matcher's copy.
134
+ */
135
+ valuesSet?: Set<string>;
136
+ isAnd?: boolean;
137
+ isOr?: boolean;
138
+ modifier?: Modifier;
139
+ isExcluded?: boolean;
140
+ /**
141
+ * Set by a consumer building filters of its own, never by the parse. A
142
+ * filter carrying it narrows nothing.
143
+ */
144
+ isOptional?: boolean;
145
+ cardTypes?: string[];
146
+ }
106
147
  /**
107
148
  * A mapping a filter key names, and what the grammar says about it: which
108
149
  * filter it is, the spelling it is named by, how its values are read, and the
@@ -124,7 +165,7 @@ export interface FilterToPropertyMapping extends CardPropertyMapping {
124
165
  /**
125
166
  * The values a card carries for this filter, as the enum they come from:
126
167
  * what a dialog offers as options and what a hint draws a suggestion from.
127
- * It is not the set of spellings the parser accepts, which is wider, since
168
+ * It is not the set of spellings the parse accepts, which is wider, since
128
169
  * the foiling, treatment, rarity, legality and meta resolvers each take
129
170
  * abbreviations of their own. A filter reading free text (`artist`, `name`,
130
171
  * `text`, `typetext`), a card name (`chain`, `references`, `referencedby`),
@@ -486,6 +527,30 @@ export declare const filtersToCardPropertyMappings: {
486
527
  isArray: true;
487
528
  partialMatch: true;
488
529
  };
530
+ printing: {
531
+ category: "print";
532
+ canonicalAlias: string;
533
+ kind: "partialMatch";
534
+ property: "setIdentifiers";
535
+ isArray: true;
536
+ partialMatch: true;
537
+ };
538
+ printings: {
539
+ category: "print";
540
+ canonicalAlias: string;
541
+ kind: "partialMatch";
542
+ property: "setIdentifiers";
543
+ isArray: true;
544
+ partialMatch: true;
545
+ };
546
+ prints: {
547
+ category: "print";
548
+ canonicalAlias: string;
549
+ kind: "partialMatch";
550
+ property: "setIdentifiers";
551
+ isArray: true;
552
+ partialMatch: true;
553
+ };
489
554
  r: {
490
555
  category: "rarity";
491
556
  canonicalAlias: string;
@@ -651,6 +716,14 @@ export declare const filtersToCardPropertyMappings: {
651
716
  property: "talents";
652
717
  isArray: true;
653
718
  };
719
+ talents: {
720
+ category: "talent";
721
+ canonicalAlias: string;
722
+ kind: "exactMatch";
723
+ vocabulary: Talent[];
724
+ property: "talents";
725
+ isArray: true;
726
+ };
654
727
  text: {
655
728
  category: "text";
656
729
  canonicalAlias: string;
@@ -741,7 +814,23 @@ export declare const filtersToCardPropertyMappings: {
741
814
  export declare const filtersToCardPropertyMappingsByKey: {
742
815
  [key: string]: FilterToPropertyMapping | undefined;
743
816
  };
744
- /** Case-folded, so a key answers however it was capitalised. */
817
+ /**
818
+ * The filter a key names, however it was capitalised, and nothing where the
819
+ * key names none.
820
+ */
821
+ export declare const getFilterMapping: (key: string) => FilterToPropertyMapping | undefined;
745
822
  export declare const getFilterCategory: (key: string) => FilterCategory | undefined;
746
823
  /** Every spelling a query can name each filter by. */
747
824
  export declare const aliasesByFilterCategory: Record<FilterCategory, readonly string[]>;
825
+ /**
826
+ * Whether the values a filter declares hold the one written against it,
827
+ * however that one was cased and punctuated.
828
+ */
829
+ export declare const getIsValueInFilterVocabulary: (category: FilterCategory, value: string) => boolean;
830
+ /**
831
+ * The spelling that names the filter a value was meant for, where exactly one
832
+ * filter other than the one it was written against declares it. Several
833
+ * filters declaring it is a choice for the reader to make rather than one to
834
+ * make on their behalf.
835
+ */
836
+ export declare const getSuggestedFilterKey: (category: FilterCategory, values: string[]) => string | undefined;
@@ -17,6 +17,7 @@ import {
17
17
  Treatment,
18
18
  Type
19
19
  } from "@flesh-and-blood/types";
20
+ import { getNormalizedFilterValue } from "./helpers.js";
20
21
  import { getLookupWithoutInheritedKeys } from "./lookups.js";
21
22
  const FilterCategory = {
22
23
  Arcane: "arcane",
@@ -390,6 +391,9 @@ const filtersToCardPropertyMappings = {
390
391
  pow: powerFilter,
391
392
  power: powerFilter,
392
393
  print: setIdentifiersFilter,
394
+ printing: setIdentifiersFilter,
395
+ printings: setIdentifiersFilter,
396
+ prints: setIdentifiersFilter,
393
397
  r: rarityFilter,
394
398
  rarity: rarityFilter,
395
399
  referencedby: referencedByFilter,
@@ -410,6 +414,7 @@ const filtersToCardPropertyMappings = {
410
414
  type: typeFilter,
411
415
  tal: talentFilter,
412
416
  talent: talentFilter,
417
+ talents: talentFilter,
413
418
  text: textFilter,
414
419
  trait: traitFilter,
415
420
  treat: treatmentFilter,
@@ -421,7 +426,8 @@ const filtersToCardPropertyMappings = {
421
426
  year: yearFilter
422
427
  };
423
428
  const filtersToCardPropertyMappingsByKey = getLookupWithoutInheritedKeys(filtersToCardPropertyMappings);
424
- const getFilterCategory = (key) => filtersToCardPropertyMappingsByKey[key.toLowerCase()]?.category;
429
+ const getFilterMapping = (key) => filtersToCardPropertyMappingsByKey[key.toLowerCase()];
430
+ const getFilterCategory = (key) => getFilterMapping(key)?.category;
425
431
  const getAliasesByFilterCategory = () => {
426
432
  const aliasesByCategory = /* @__PURE__ */ Object.create(null);
427
433
  for (const [alias, { category }] of Object.entries(
@@ -437,6 +443,53 @@ const getAliasesByFilterCategory = () => {
437
443
  return aliasesByCategory;
438
444
  };
439
445
  const aliasesByFilterCategory = getAliasesByFilterCategory();
446
+ const getFilterMappingsByVocabularyValue = () => {
447
+ const mappingsByVocabularyValue = /* @__PURE__ */ Object.create(null);
448
+ const walkedMappings = /* @__PURE__ */ new Set();
449
+ const mappings = Object.values(
450
+ filtersToCardPropertyMappings
451
+ );
452
+ for (const mapping of mappings) {
453
+ const { vocabulary } = mapping;
454
+ if (vocabulary && !walkedMappings.has(mapping)) {
455
+ walkedMappings.add(mapping);
456
+ for (const vocabularyValue of vocabulary) {
457
+ const normalizedValue = getNormalizedFilterValue(vocabularyValue);
458
+ const mappingsHoldingValue = mappingsByVocabularyValue[normalizedValue];
459
+ if (mappingsHoldingValue) {
460
+ mappingsHoldingValue.push(mapping);
461
+ } else {
462
+ mappingsByVocabularyValue[normalizedValue] = [mapping];
463
+ }
464
+ }
465
+ }
466
+ }
467
+ return mappingsByVocabularyValue;
468
+ };
469
+ const filterMappingsByVocabularyValue = getFilterMappingsByVocabularyValue();
470
+ const getIsValueInFilterVocabulary = (category, value) => !!filterMappingsByVocabularyValue[getNormalizedFilterValue(value)]?.some(
471
+ (mapping) => mapping.category === category
472
+ );
473
+ const getFilterMappingsHoldingValues = (category, values) => {
474
+ let mappingsHoldingValues = [];
475
+ let isFirstValue = true;
476
+ for (const value of values) {
477
+ const mappingsHoldingValue = (filterMappingsByVocabularyValue[getNormalizedFilterValue(value)] || []).filter((mapping) => mapping.category !== category);
478
+ mappingsHoldingValues = isFirstValue ? mappingsHoldingValue : mappingsHoldingValues.filter(
479
+ (mapping) => mappingsHoldingValue.includes(mapping)
480
+ );
481
+ isFirstValue = false;
482
+ }
483
+ return mappingsHoldingValues;
484
+ };
485
+ const getSuggestedFilterKey = (category, values) => {
486
+ const mappingsHoldingValues = getFilterMappingsHoldingValues(
487
+ category,
488
+ values
489
+ );
490
+ const [suggestedMapping] = mappingsHoldingValues;
491
+ return mappingsHoldingValues.length === 1 ? suggestedMapping.canonicalAlias : void 0;
492
+ };
440
493
  export {
441
494
  FilterCategory,
442
495
  FilterKind,
@@ -446,5 +499,8 @@ export {
446
499
  availableModifiers,
447
500
  filtersToCardPropertyMappings,
448
501
  filtersToCardPropertyMappingsByKey,
449
- getFilterCategory
502
+ getFilterCategory,
503
+ getFilterMapping,
504
+ getIsValueInFilterVocabulary,
505
+ getSuggestedFilterKey
450
506
  };
@@ -0,0 +1,49 @@
1
+ import { Foiling, Hero, Rarity, Release, Treatment } from "@flesh-and-blood/types";
2
+ import { FilterCategory, type AppliedFilter, type FilterToPropertyMapping, type FilterValue } from "./filterMappings.js";
3
+ import { CatalogueIndex } from "./searchIndex.js";
4
+ /**
5
+ * What a query says about the printings each result renders, and about their
6
+ * order. They narrow no further which cards answer.
7
+ */
8
+ export interface QueryAttributes {
9
+ artists: string[];
10
+ foilings: Foiling[];
11
+ isExpansionSlot: boolean;
12
+ prints: string[];
13
+ rarities: Rarity[];
14
+ releases: Release[];
15
+ treatments: Treatment[];
16
+ }
17
+ /** A filter term to resolve: the filter it names, and what it asks of it. */
18
+ export interface FilterTerm {
19
+ category: FilterCategory;
20
+ filterValues: FilterValue[];
21
+ isAnd: boolean;
22
+ isExcluded: boolean;
23
+ key: string;
24
+ mapping: FilterToPropertyMapping;
25
+ }
26
+ /** What a resolver reads besides the term itself. */
27
+ export interface FilterResolverContext {
28
+ additionalHeroes: Hero[];
29
+ additionalSets: Release[];
30
+ index: CatalogueIndex;
31
+ today: string;
32
+ }
33
+ export interface FilterResolution {
34
+ appliedFilters: AppliedFilter[];
35
+ attributes?: Partial<QueryAttributes>;
36
+ /**
37
+ * The values the filter reads in place of the ones written, where a resolver
38
+ * read them as others: `foil:cf` as cold, `p:red` as a pitch of 1. Absent
39
+ * where the filter matches on the values as they were written.
40
+ */
41
+ canonicalValues?: string[];
42
+ /** The values naming nothing the filter reads, as they were written. */
43
+ unresolvedValues: string[];
44
+ }
45
+ export declare const RARITY_VALUES_MAPPING: {
46
+ [key: string]: Rarity;
47
+ };
48
+ /** What one filter term asks for, whichever filter it names. */
49
+ export declare const getFilterResolution: (term: FilterTerm, context: FilterResolverContext) => FilterResolution;