@lde/search 0.30.0 → 0.31.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/dist/adapter.d.ts +3 -3
- package/dist/adapter.d.ts.map +1 -1
- package/dist/adapter.js +2 -2
- package/dist/engine.d.ts +20 -4
- package/dist/engine.d.ts.map +1 -1
- package/dist/query.d.ts +49 -1
- package/dist/query.d.ts.map +1 -1
- package/dist/query.js +79 -0
- package/dist/testing.d.ts.map +1 -1
- package/dist/testing.js +39 -0
- package/package.json +1 -1
package/dist/adapter.d.ts
CHANGED
|
@@ -6,12 +6,12 @@
|
|
|
6
6
|
* deployment author declaring a schema and running searches needs none of
|
|
7
7
|
* this; the main entry point carries that (small) authoring surface.
|
|
8
8
|
*/
|
|
9
|
-
export { assertTypeInSchema, physicalFields, irAlias, displayFieldName, displayFieldPattern, displayLangOf, searchableFields, facetableFields, filterableFields, sortableFields, outputFields, isInternalField, isInlineReference, nestedReferenceType, nestedFieldName, localLookupTargetsOf, referencedTargetsOf, storedTargetOf, TARGET_FIELD, identityFieldOf, identityFieldName, referenceFields, referenceTypeNamed, rootTypeNamed, inlineFramingDepth, fieldNamed, datasetField, ID_FIELD, AND_KEY, OR_KEY, labelFieldOf, labelFieldNameOf, labelSourceNamesOf, labelTargetNamesOf, documentKeyOf, DEFAULT_LABEL_FIELD, isRangeFacet, isBinnedRangeFacet, isAbsoluteIri, isoToUnixSeconds, unixSecondsToIso, } from './schema.js';
|
|
9
|
+
export { assertTypeInSchema, physicalFields, irAlias, displayFieldName, displayFieldPattern, displayLangOf, searchableFields, facetableFields, filterableFields, sortableFields, outputFields, isInternalField, isInlineReference, nestedReferenceType, nestedFieldName, localLookupTargetsOf, referencedTargetsOf, storedTargetOf, TARGET_FIELD, identityFieldOf, identityFieldName, inheritedFacetPolicies, referenceFields, referenceTypeNamed, rootTypeNamed, inlineFramingDepth, fieldNamed, datasetField, ID_FIELD, AND_KEY, OR_KEY, labelFieldOf, labelFieldNameOf, labelSourceNamesOf, labelTargetNamesOf, documentKeyOf, DEFAULT_LABEL_FIELD, isRangeFacet, isBinnedRangeFacet, isAbsoluteIri, isoToUnixSeconds, unixSecondsToIso, } from './schema.js';
|
|
10
10
|
export type { PhysicalFields } from './schema.js';
|
|
11
11
|
export { physicalNameTokens } from './physical-name.js';
|
|
12
12
|
export { matchLanguage } from './language.js';
|
|
13
13
|
export { joinGraph, MAX_JOIN_DEPTH } from './join-graph.js';
|
|
14
14
|
export type { JoinGraph } from './join-graph.js';
|
|
15
|
-
export { filterOperatorFor, filterOperator, filterOn, isWelded, isUnsatisfiable, validateQuery, assertValidQuery, resolvePath, pageForOffset, } from './query.js';
|
|
16
|
-
export type { FilterOperator, QueryIssue, ResolvedPath, PathFailure, } from './query.js';
|
|
15
|
+
export { filterOperatorFor, filterOperator, filterOn, isWelded, isUnsatisfiable, facetSearches, validateQuery, assertValidQuery, resolvePath, pageForOffset, } from './query.js';
|
|
16
|
+
export type { FacetSearch, FilterOperator, QueryIssue, ResolvedPath, PathFailure, } from './query.js';
|
|
17
17
|
//# sourceMappingURL=adapter.d.ts.map
|
package/dist/adapter.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"adapter.d.ts","sourceRoot":"","sources":["../src/adapter.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,OAAO,EACL,kBAAkB,EAClB,cAAc,EACd,OAAO,EACP,gBAAgB,EAChB,mBAAmB,EACnB,aAAa,EACb,gBAAgB,EAChB,eAAe,EACf,gBAAgB,EAChB,cAAc,EACd,YAAY,EACZ,eAAe,EACf,iBAAiB,EACjB,mBAAmB,EACnB,eAAe,EACf,oBAAoB,EACpB,mBAAmB,EACnB,cAAc,EACd,YAAY,EACZ,eAAe,EACf,iBAAiB,EACjB,eAAe,EACf,kBAAkB,EAClB,aAAa,EACb,kBAAkB,EAClB,UAAU,EACV,YAAY,EACZ,QAAQ,EACR,OAAO,EACP,MAAM,EACN,YAAY,EACZ,gBAAgB,EAChB,kBAAkB,EAClB,kBAAkB,EAClB,aAAa,EACb,mBAAmB,EACnB,YAAY,EACZ,kBAAkB,EAClB,aAAa,EACb,gBAAgB,EAChB,gBAAgB,GACjB,MAAM,aAAa,CAAC;AACrB,YAAY,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAClD,OAAO,EAAE,kBAAkB,EAAE,MAAM,oBAAoB,CAAC;AACxD,OAAO,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AAC9C,OAAO,EAAE,SAAS,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AAC5D,YAAY,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AACjD,OAAO,EACL,iBAAiB,EACjB,cAAc,EACd,QAAQ,EACR,QAAQ,EACR,eAAe,EACf,aAAa,EACb,gBAAgB,EAChB,WAAW,EACX,aAAa,GACd,MAAM,YAAY,CAAC;AACpB,YAAY,EACV,cAAc,EACd,UAAU,EACV,YAAY,EACZ,WAAW,GACZ,MAAM,YAAY,CAAC"}
|
|
1
|
+
{"version":3,"file":"adapter.d.ts","sourceRoot":"","sources":["../src/adapter.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,OAAO,EACL,kBAAkB,EAClB,cAAc,EACd,OAAO,EACP,gBAAgB,EAChB,mBAAmB,EACnB,aAAa,EACb,gBAAgB,EAChB,eAAe,EACf,gBAAgB,EAChB,cAAc,EACd,YAAY,EACZ,eAAe,EACf,iBAAiB,EACjB,mBAAmB,EACnB,eAAe,EACf,oBAAoB,EACpB,mBAAmB,EACnB,cAAc,EACd,YAAY,EACZ,eAAe,EACf,iBAAiB,EACjB,sBAAsB,EACtB,eAAe,EACf,kBAAkB,EAClB,aAAa,EACb,kBAAkB,EAClB,UAAU,EACV,YAAY,EACZ,QAAQ,EACR,OAAO,EACP,MAAM,EACN,YAAY,EACZ,gBAAgB,EAChB,kBAAkB,EAClB,kBAAkB,EAClB,aAAa,EACb,mBAAmB,EACnB,YAAY,EACZ,kBAAkB,EAClB,aAAa,EACb,gBAAgB,EAChB,gBAAgB,GACjB,MAAM,aAAa,CAAC;AACrB,YAAY,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAClD,OAAO,EAAE,kBAAkB,EAAE,MAAM,oBAAoB,CAAC;AACxD,OAAO,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AAC9C,OAAO,EAAE,SAAS,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AAC5D,YAAY,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AACjD,OAAO,EACL,iBAAiB,EACjB,cAAc,EACd,QAAQ,EACR,QAAQ,EACR,eAAe,EACf,aAAa,EACb,aAAa,EACb,gBAAgB,EAChB,WAAW,EACX,aAAa,GACd,MAAM,YAAY,CAAC;AACpB,YAAY,EACV,WAAW,EACX,cAAc,EACd,UAAU,EACV,YAAY,EACZ,WAAW,GACZ,MAAM,YAAY,CAAC"}
|
package/dist/adapter.js
CHANGED
|
@@ -6,8 +6,8 @@
|
|
|
6
6
|
* deployment author declaring a schema and running searches needs none of
|
|
7
7
|
* this; the main entry point carries that (small) authoring surface.
|
|
8
8
|
*/
|
|
9
|
-
export { assertTypeInSchema, physicalFields, irAlias, displayFieldName, displayFieldPattern, displayLangOf, searchableFields, facetableFields, filterableFields, sortableFields, outputFields, isInternalField, isInlineReference, nestedReferenceType, nestedFieldName, localLookupTargetsOf, referencedTargetsOf, storedTargetOf, TARGET_FIELD, identityFieldOf, identityFieldName, referenceFields, referenceTypeNamed, rootTypeNamed, inlineFramingDepth, fieldNamed, datasetField, ID_FIELD, AND_KEY, OR_KEY, labelFieldOf, labelFieldNameOf, labelSourceNamesOf, labelTargetNamesOf, documentKeyOf, DEFAULT_LABEL_FIELD, isRangeFacet, isBinnedRangeFacet, isAbsoluteIri, isoToUnixSeconds, unixSecondsToIso, } from './schema.js';
|
|
9
|
+
export { assertTypeInSchema, physicalFields, irAlias, displayFieldName, displayFieldPattern, displayLangOf, searchableFields, facetableFields, filterableFields, sortableFields, outputFields, isInternalField, isInlineReference, nestedReferenceType, nestedFieldName, localLookupTargetsOf, referencedTargetsOf, storedTargetOf, TARGET_FIELD, identityFieldOf, identityFieldName, inheritedFacetPolicies, referenceFields, referenceTypeNamed, rootTypeNamed, inlineFramingDepth, fieldNamed, datasetField, ID_FIELD, AND_KEY, OR_KEY, labelFieldOf, labelFieldNameOf, labelSourceNamesOf, labelTargetNamesOf, documentKeyOf, DEFAULT_LABEL_FIELD, isRangeFacet, isBinnedRangeFacet, isAbsoluteIri, isoToUnixSeconds, unixSecondsToIso, } from './schema.js';
|
|
10
10
|
export { physicalNameTokens } from './physical-name.js';
|
|
11
11
|
export { matchLanguage } from './language.js';
|
|
12
12
|
export { joinGraph, MAX_JOIN_DEPTH } from './join-graph.js';
|
|
13
|
-
export { filterOperatorFor, filterOperator, filterOn, isWelded, isUnsatisfiable, validateQuery, assertValidQuery, resolvePath, pageForOffset, } from './query.js';
|
|
13
|
+
export { filterOperatorFor, filterOperator, filterOn, isWelded, isUnsatisfiable, facetSearches, validateQuery, assertValidQuery, resolvePath, pageForOffset, } from './query.js';
|
package/dist/engine.d.ts
CHANGED
|
@@ -24,6 +24,13 @@ import type { RootType, RootTypeOf, SearchSchema, SearchType } from './schema.js
|
|
|
24
24
|
* Validation is not the caller’s job: it must hold for every surface and for
|
|
25
25
|
* injected deployment policy.
|
|
26
26
|
*
|
|
27
|
+
* Facets mean the same on every engine: a facet counts the documents under
|
|
28
|
+
* every `where` clause except its own (skip-own-filter), and reports every
|
|
29
|
+
* value its own clause selects, at count 0 when nothing else leaves it a
|
|
30
|
+
* document ({@link SearchQuery.facets}). An adapter plans the searches with
|
|
31
|
+
* `facetSearches` from `@lde/search/adapter`, so ownership cannot drift
|
|
32
|
+
* between engines.
|
|
33
|
+
*
|
|
27
34
|
* `Types` is the literal tuple {@link searchSchema} captured: with a
|
|
28
35
|
* `defineSearchType` declaration, `search()` only accepts the deployment’s
|
|
29
36
|
* own types (a foreign type is a compile error) and returns facet/document
|
|
@@ -34,14 +41,22 @@ export interface SearchEngine<Types extends readonly SearchType[] = readonly Sea
|
|
|
34
41
|
/** The declaration this engine serves – exposed so a surface can route and
|
|
35
42
|
* a caller can enumerate the searchable types. */
|
|
36
43
|
readonly schema: SearchSchema<Types>;
|
|
44
|
+
/**
|
|
45
|
+
* Answer one query: its hits, total and facets. The call fails as a whole:
|
|
46
|
+
* when any search an engine needs for it fails – including one that counts
|
|
47
|
+
* a single facet – the returned promise rejects and no hits come back. A
|
|
48
|
+
* caller that must keep its hits when facets fail asks for no facets here
|
|
49
|
+
* and gets them from {@link searchFacets}, which reports failures per query.
|
|
50
|
+
*/
|
|
37
51
|
search<T extends RootTypeOf<Types>>(searchType: T, query: SearchQuery): Promise<SearchResult<FacetFieldsOf<T>, OutputFieldsOf<T>>>;
|
|
38
52
|
/**
|
|
39
53
|
* The batch entry point: answer several facet-only queries against one type
|
|
40
54
|
* in a single engine round-trip (where the engine supports one, e.g.
|
|
41
55
|
* Typesense `multi_search`), returning one {@link FacetsOutcome} per query,
|
|
42
|
-
* positionally aligned with `queries`. This is what keeps a
|
|
43
|
-
*
|
|
44
|
-
* fanning out into
|
|
56
|
+
* positionally aligned with `queries`. This is what keeps a sidebar that
|
|
57
|
+
* needs facets over more than one query – over the listing’s filters and
|
|
58
|
+
* over the whole collection – from fanning out into several engine calls.
|
|
59
|
+
* Only facets come back: an
|
|
45
60
|
* engine answers every query in the batch facet-only (as if `limit: 0` and
|
|
46
61
|
* without ordering, whatever the query carries), so hits are never
|
|
47
62
|
* transferred. Reference-facet buckets carry resolved labels exactly as in
|
|
@@ -59,7 +74,8 @@ export interface SearchEngine<Types extends readonly SearchType[] = readonly Sea
|
|
|
59
74
|
* facets, or the error that failed it. The `allSettled`-style shape
|
|
60
75
|
* keeps one failed query (e.g. one failed `multi_search` entry) from
|
|
61
76
|
* discarding its siblings’ results: a caller degrades exactly the facets of
|
|
62
|
-
* the failed query
|
|
77
|
+
* the failed query – all of them, also when the engine needed several
|
|
78
|
+
* searches to answer it. Discriminate with `'error' in outcome`.
|
|
63
79
|
*/
|
|
64
80
|
export type FacetsOutcome<FacetField extends string = string> = {
|
|
65
81
|
readonly facets: FacetMap<FacetField>;
|
package/dist/engine.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"engine.d.ts","sourceRoot":"","sources":["../src/engine.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAC9C,OAAO,KAAK,EACV,QAAQ,EACR,UAAU,EACV,YAAY,EACZ,UAAU,EACX,MAAM,aAAa,CAAC;AAErB
|
|
1
|
+
{"version":3,"file":"engine.d.ts","sourceRoot":"","sources":["../src/engine.ts"],"names":[],"mappings":"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"}
|
package/dist/query.d.ts
CHANGED
|
@@ -16,7 +16,14 @@ export interface SearchQuery {
|
|
|
16
16
|
/** Numbered pagination. */
|
|
17
17
|
readonly limit: number;
|
|
18
18
|
readonly offset: number;
|
|
19
|
-
/**
|
|
19
|
+
/**
|
|
20
|
+
* Logical field names to return facets for. Each facet counts the documents
|
|
21
|
+
* under every `where` clause except its **own** (skip-own-filter), so a
|
|
22
|
+
* facet the user selected in still offers its other values; and it reports
|
|
23
|
+
* every value its own clause selects, with count 0 when the other clauses
|
|
24
|
+
* leave that value no documents, so a selection can always be undone. See
|
|
25
|
+
* {@link facetSearches} for what a clause owns.
|
|
26
|
+
*/
|
|
20
27
|
readonly facets: readonly string[];
|
|
21
28
|
/** Selects the per-locale fields to query/sort on (from `Accept-Language`). */
|
|
22
29
|
readonly locale: string;
|
|
@@ -178,6 +185,47 @@ export declare function filterOn(criterion: Criterion): Filter;
|
|
|
178
185
|
* than dispatching it.
|
|
179
186
|
*/
|
|
180
187
|
export declare function isUnsatisfiable(query: SearchQuery): boolean;
|
|
188
|
+
/**
|
|
189
|
+
* One search a query’s facets need, with the values each of its facets must
|
|
190
|
+
* report whatever their count. See {@link facetSearches}.
|
|
191
|
+
*/
|
|
192
|
+
export interface FacetSearch {
|
|
193
|
+
/** The query to facet on: `where` without its facets’ own clauses, and
|
|
194
|
+
* `facets` the fields this search counts. */
|
|
195
|
+
readonly query: SearchQuery;
|
|
196
|
+
/** Per facet, the values its own clause selects. Only an own-filtered
|
|
197
|
+
* facet has an entry, and only an `in` or `is` selection names values. */
|
|
198
|
+
readonly selected: Readonly<Record<string, readonly string[]>>;
|
|
199
|
+
/** The facet whose own clauses this search removed; absent on the search
|
|
200
|
+
* that every facet without a clause of its own shares. */
|
|
201
|
+
readonly own?: string;
|
|
202
|
+
}
|
|
203
|
+
/**
|
|
204
|
+
* The fewest searches that compute a query’s {@link SearchQuery.facets} the way
|
|
205
|
+
* the port defines them: each facet counts the documents under every `where`
|
|
206
|
+
* clause except its own. Every facet whose field carries no clause of its own
|
|
207
|
+
* shares the query’s `where` – one search for all of them, so an unfiltered
|
|
208
|
+
* browse is a single search – and each own-filtered facet gets a search with
|
|
209
|
+
* its own clauses removed, together with the values those clauses selected.
|
|
210
|
+
* The searches keep the query’s text, paging and order; an adapter answering
|
|
211
|
+
* facets alone strips the hits itself. An adapter answers each facet from the
|
|
212
|
+
* search that counts it, so every engine computes facets alike.
|
|
213
|
+
*
|
|
214
|
+
* A clause is a facet’s **own** when every criterion in it names that field,
|
|
215
|
+
* on the searched type: an ordinary filter, and equally a same-field
|
|
216
|
+
* disjunction (`or: [{ created: { max: … } }, { created: { min: … } }]`).
|
|
217
|
+
* Those are a selection on one axis, and keeping them would offer back only
|
|
218
|
+
* what the user already picked. A clause spanning several fields is nobody’s
|
|
219
|
+
* own: it constrains the document as a whole, never one axis, so it always
|
|
220
|
+
* stays, and every facet counts the same documents under it. A criterion
|
|
221
|
+
* reached through a join path is a different axis than the local field of the
|
|
222
|
+
* same name ([ADR 18](../../docs/decisions/0018-filter-across-several-fields-with-one-clause.md)).
|
|
223
|
+
*
|
|
224
|
+
* A default a surface adds to `where` (a valid-only `status`) is a clause
|
|
225
|
+
* like any other: its facet counts across every value and reports the
|
|
226
|
+
* default’s values as selected.
|
|
227
|
+
*/
|
|
228
|
+
export declare function facetSearches(query: SearchQuery): FacetSearch[];
|
|
181
229
|
/**
|
|
182
230
|
* One structural problem {@link validateQuery} found: the query references a
|
|
183
231
|
* field the search type does not declare, uses it in a role it does not opt
|
package/dist/query.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"query.d.ts","sourceRoot":"","sources":["../src/query.ts"],"names":[],"mappings":"AAAA,OAAO,EAQL,KAAK,YAAY,EACjB,KAAK,UAAU,EAChB,MAAM,aAAa,CAAC;AACrB,OAAO,EAAkB,KAAK,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAEjE;;;;;GAKG;AACH,MAAM,WAAW,WAAW;IAC1B,wEAAwE;IACxE,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC;IACvB,yEAAyE;IACzE,QAAQ,CAAC,KAAK,EAAE,SAAS,MAAM,EAAE,CAAC;IAClC,2EAA2E;IAC3E,QAAQ,CAAC,OAAO,EAAE,SAAS,IAAI,EAAE,CAAC;IAClC,2BAA2B;IAC3B,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB
|
|
1
|
+
{"version":3,"file":"query.d.ts","sourceRoot":"","sources":["../src/query.ts"],"names":[],"mappings":"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"}
|
package/dist/query.js
CHANGED
|
@@ -54,6 +54,62 @@ export function isUnsatisfiable(query) {
|
|
|
54
54
|
criterion.in.length === 0);
|
|
55
55
|
});
|
|
56
56
|
}
|
|
57
|
+
/**
|
|
58
|
+
* The fewest searches that compute a query’s {@link SearchQuery.facets} the way
|
|
59
|
+
* the port defines them: each facet counts the documents under every `where`
|
|
60
|
+
* clause except its own. Every facet whose field carries no clause of its own
|
|
61
|
+
* shares the query’s `where` – one search for all of them, so an unfiltered
|
|
62
|
+
* browse is a single search – and each own-filtered facet gets a search with
|
|
63
|
+
* its own clauses removed, together with the values those clauses selected.
|
|
64
|
+
* The searches keep the query’s text, paging and order; an adapter answering
|
|
65
|
+
* facets alone strips the hits itself. An adapter answers each facet from the
|
|
66
|
+
* search that counts it, so every engine computes facets alike.
|
|
67
|
+
*
|
|
68
|
+
* A clause is a facet’s **own** when every criterion in it names that field,
|
|
69
|
+
* on the searched type: an ordinary filter, and equally a same-field
|
|
70
|
+
* disjunction (`or: [{ created: { max: … } }, { created: { min: … } }]`).
|
|
71
|
+
* Those are a selection on one axis, and keeping them would offer back only
|
|
72
|
+
* what the user already picked. A clause spanning several fields is nobody’s
|
|
73
|
+
* own: it constrains the document as a whole, never one axis, so it always
|
|
74
|
+
* stays, and every facet counts the same documents under it. A criterion
|
|
75
|
+
* reached through a join path is a different axis than the local field of the
|
|
76
|
+
* same name ([ADR 18](../../docs/decisions/0018-filter-across-several-fields-with-one-clause.md)).
|
|
77
|
+
*
|
|
78
|
+
* A default a surface adds to `where` (a valid-only `status`) is a clause
|
|
79
|
+
* like any other: its facet counts across every value and reports the
|
|
80
|
+
* default’s values as selected.
|
|
81
|
+
*/
|
|
82
|
+
export function facetSearches(query) {
|
|
83
|
+
const owners = query.where.map(ownedField);
|
|
84
|
+
const owned = new Set(owners.filter((field) => field !== undefined));
|
|
85
|
+
const facets = [...new Set(query.facets)];
|
|
86
|
+
const shared = facets.filter((field) => !owned.has(field));
|
|
87
|
+
const searches = shared.length > 0
|
|
88
|
+
? [{ query: { ...query, facets: shared }, selected: {} }]
|
|
89
|
+
: [];
|
|
90
|
+
for (const field of facets.filter((field) => owned.has(field))) {
|
|
91
|
+
const own = query.where.filter((_, index) => owners[index] === field);
|
|
92
|
+
const selected = [
|
|
93
|
+
...new Set(own
|
|
94
|
+
.flatMap((filter) => filter.or)
|
|
95
|
+
.flatMap((criterion) => 'in' in criterion
|
|
96
|
+
? criterion.in
|
|
97
|
+
: 'is' in criterion
|
|
98
|
+
? [String(criterion.is)]
|
|
99
|
+
: [])),
|
|
100
|
+
];
|
|
101
|
+
searches.push({
|
|
102
|
+
query: {
|
|
103
|
+
...query,
|
|
104
|
+
where: query.where.filter((_, index) => owners[index] !== field),
|
|
105
|
+
facets: [field],
|
|
106
|
+
},
|
|
107
|
+
selected: selected.length > 0 ? { [field]: selected } : {},
|
|
108
|
+
own: field,
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
return searches;
|
|
112
|
+
}
|
|
57
113
|
/**
|
|
58
114
|
* The checks every leaf criterion answers to, wherever it sits: the field is
|
|
59
115
|
* declared and `filterable` on the type it constrains, its operator matches
|
|
@@ -368,3 +424,26 @@ export function assertValidQuery(query, searchType, schema, joins) {
|
|
|
368
424
|
export function pageForOffset(offset, limit) {
|
|
369
425
|
return limit > 0 ? Math.floor(offset / limit) + 1 : 1;
|
|
370
426
|
}
|
|
427
|
+
/**
|
|
428
|
+
* The field a clause belongs to, or `undefined` when it belongs to none: every
|
|
429
|
+
* criterion must constrain the same axis ({@link axisOf}).
|
|
430
|
+
*/
|
|
431
|
+
function ownedField(filter) {
|
|
432
|
+
const [first, ...rest] = filter.or;
|
|
433
|
+
if (first === undefined) {
|
|
434
|
+
return undefined;
|
|
435
|
+
}
|
|
436
|
+
const key = axisOf(first);
|
|
437
|
+
return rest.every((criterion) => axisOf(criterion) === key) ? key : undefined;
|
|
438
|
+
}
|
|
439
|
+
/**
|
|
440
|
+
* The axis one criterion constrains: its field, qualified by the join path it
|
|
441
|
+
* reaches that field through. `dataset.publisher` and a local `publisher` are
|
|
442
|
+
* different questions, so a selection on one is no selection on the other.
|
|
443
|
+
* Facets are not offered on joined fields, so a joined clause never matches a
|
|
444
|
+
* facet and simply never drops.
|
|
445
|
+
*/
|
|
446
|
+
function axisOf(criterion) {
|
|
447
|
+
const on = criterion.on ?? [];
|
|
448
|
+
return on.length === 0 ? criterion.field : [...on, criterion.field].join('.');
|
|
449
|
+
}
|
package/dist/testing.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"testing.d.ts","sourceRoot":"","sources":["../src/testing.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAuBhD;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,4BAA4B,CAC1C,IAAI,EAAE,MAAM,EACZ,MAAM,EAAE,MAAM,YAAY,GACzB,IAAI,
|
|
1
|
+
{"version":3,"file":"testing.d.ts","sourceRoot":"","sources":["../src/testing.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAuBhD;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,4BAA4B,CAC1C,IAAI,EAAE,MAAM,EACZ,MAAM,EAAE,MAAM,YAAY,GACzB,IAAI,CA8WN"}
|
package/dist/testing.js
CHANGED
|
@@ -179,6 +179,45 @@ export function describeSearchEngineContract(name, engine) {
|
|
|
179
179
|
}
|
|
180
180
|
}
|
|
181
181
|
});
|
|
182
|
+
it('counts a facet without its own clause, and reports what that clause selects', async () => {
|
|
183
|
+
// Selecting a value no document carries leaves no hits. The facet still
|
|
184
|
+
// offers every other value, counted as if nothing were selected, and
|
|
185
|
+
// keeps the selected one at count 0 so it can be undone.
|
|
186
|
+
const absent = 'urn:lde:testing:absent-facet-value';
|
|
187
|
+
for (const searchType of types()) {
|
|
188
|
+
const field = facetableFields(searchType).find((candidate) => candidate.filterable === true &&
|
|
189
|
+
filterOperatorFor(candidate.kind) === 'in');
|
|
190
|
+
if (field === undefined) {
|
|
191
|
+
continue;
|
|
192
|
+
}
|
|
193
|
+
const facetQuery = {
|
|
194
|
+
...browse(searchType),
|
|
195
|
+
facets: [field.name],
|
|
196
|
+
};
|
|
197
|
+
const selected = {
|
|
198
|
+
...facetQuery,
|
|
199
|
+
where: [filterOn({ field: field.name, in: [absent] })],
|
|
200
|
+
};
|
|
201
|
+
const [unfiltered, filtered] = await engine().searchFacets(searchType, [
|
|
202
|
+
facetQuery,
|
|
203
|
+
selected,
|
|
204
|
+
]);
|
|
205
|
+
if ('error' in unfiltered || 'error' in filtered) {
|
|
206
|
+
throw new Error('Expected facets outcomes.');
|
|
207
|
+
}
|
|
208
|
+
const others = unfiltered.facets[field.name]?.buckets ?? [];
|
|
209
|
+
const result = await engine().search(searchType, selected);
|
|
210
|
+
expect(result.total).toBe(0);
|
|
211
|
+
for (const buckets of [
|
|
212
|
+
filtered.facets[field.name]?.buckets ?? [],
|
|
213
|
+
result.facets[field.name]?.buckets ?? [],
|
|
214
|
+
]) {
|
|
215
|
+
expect(buckets).toHaveLength(others.length + 1);
|
|
216
|
+
expect(buckets).toEqual(expect.arrayContaining([...others]));
|
|
217
|
+
expect(buckets).toContainEqual(expect.objectContaining({ value: absent, count: 0 }));
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
});
|
|
182
221
|
it('resolves an empty searchFacets batch to an empty list', async () => {
|
|
183
222
|
for (const searchType of types()) {
|
|
184
223
|
await expect(engine().searchFacets(searchType, [])).resolves.toEqual([]);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lde/search",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.31.0",
|
|
4
4
|
"description": "Engine- and domain-agnostic core for RDF-backed search: a unified declarative field model (SearchField/SearchType/SearchSchema), a neutral query IR, the SearchEngine port with logical result types, and a streaming CONSTRUCT-to-document projection. Bakes in no engine, protocol, or domain.",
|
|
5
5
|
"repository": {
|
|
6
6
|
"url": "git+https://github.com/ldelements/lde.git",
|