@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.
- package/dist/filterMappings.d.ts +93 -4
- package/dist/filterMappings.js +58 -2
- package/dist/filterResolvers.d.ts +49 -0
- package/dist/filterResolvers.js +536 -0
- package/dist/filters.d.ts +12 -30
- package/dist/filters.js +9 -645
- package/dist/helpers.d.ts +6 -0
- package/dist/helpers.js +2 -0
- package/dist/index.cjs +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/metaFilters.d.ts +27 -12
- package/dist/metaFilters.js +85 -118
- package/dist/queryParse.d.ts +71 -0
- package/dist/queryParse.js +250 -0
- package/dist/queryTokens.d.ts +17 -0
- package/dist/queryTokens.js +65 -5
- package/dist/search.d.ts +9 -9
- package/dist/search.js +68 -123
- package/package.json +3 -3
package/dist/filterMappings.d.ts
CHANGED
|
@@ -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
|
|
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
|
|
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
|
|
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
|
-
/**
|
|
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;
|
package/dist/filterMappings.js
CHANGED
|
@@ -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
|
|
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;
|