@lde/search 0.23.1 → 0.24.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.
@@ -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;AAmBhD;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,4BAA4B,CAC1C,IAAI,EAAE,MAAM,EACZ,MAAM,EAAE,MAAM,YAAY,GACzB,IAAI,CAuON"}
1
+ {"version":3,"file":"testing.d.ts","sourceRoot":"","sources":["../src/testing.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAoBhD;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,4BAA4B,CAC1C,IAAI,EAAE,MAAM,EACZ,MAAM,EAAE,MAAM,YAAY,GACzB,IAAI,CAuRN"}
package/dist/testing.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import { describe, expect, it } from 'vitest';
2
2
  import { filterOn, filterOperatorFor, } from './query.js';
3
- import { facetableFields, filterableFields, ID_FIELD, nestedReferenceType, outputFields, } from './schema.js';
3
+ import { facetableFields, filterableFields, ID_FIELD, inheritedFacetKeys, nestedReferenceType, outputFields, } from './schema.js';
4
4
  /**
5
5
  * The executable {@link SearchEngine} port contract (import from
6
6
  * `@lde/search/testing`): a vitest suite every engine adapter runs against a
@@ -200,6 +200,53 @@ export function describeSearchEngineContract(name, engine) {
200
200
  }
201
201
  }
202
202
  });
203
+ it('buckets a facet inheriting a policy on admitted keys only, labelled, while filtering stays exact', async () => {
204
+ // A facet policy (`RootType.facetKeys`) narrows the FACET and nothing
205
+ // else. Two things an adapter can get wrong, each silently: facet the
206
+ // companion field but report its buckets under the engine’s name, which
207
+ // loses every label and files the counts under a key no consumer asked
208
+ // for; and pick the filter operator off the declaration, so a `where`
209
+ // on an excluded value – still `facetable` in the schema, no longer a
210
+ // facet in the engine – partial-matches on a shared path prefix. The
211
+ // adapter’s fixture should carry two IRIs sharing a prefix so the
212
+ // second assertion bites.
213
+ for (const searchType of types()) {
214
+ for (const field of facetableFields(searchType)) {
215
+ const policy = inheritedFacetKeys(field, engine().schema);
216
+ if (policy === undefined) {
217
+ continue;
218
+ }
219
+ const result = await engine().search(searchType, {
220
+ ...browse(searchType),
221
+ limit: 20,
222
+ facets: [field.name],
223
+ });
224
+ const buckets = result.facets[field.name] ?? [];
225
+ for (const bucket of buckets) {
226
+ expect(policy.only(bucket.value)).toBe(true);
227
+ expect(bucket.label).toBeDefined();
228
+ }
229
+ if (field.output !== true || field.filterable !== true) {
230
+ continue; // Nothing to read the stored values back from.
231
+ }
232
+ const excluded = result.hits
233
+ .flatMap((hit) => referenceIds(hit.document[field.name]))
234
+ .find((id) => !policy.only(id));
235
+ if (excluded === undefined) {
236
+ continue;
237
+ }
238
+ const filtered = await engine().search(searchType, {
239
+ ...browse(searchType),
240
+ limit: 20,
241
+ where: [filterOn({ field: field.name, in: [excluded] })],
242
+ });
243
+ expect(filtered.total).toBeGreaterThan(0);
244
+ for (const hit of filtered.hits) {
245
+ expect(referenceIds(hit.document[field.name])).toContain(excluded);
246
+ }
247
+ }
248
+ }
249
+ });
203
250
  it('answers a browse query with hits, a total and facets', async () => {
204
251
  for (const searchType of types()) {
205
252
  const result = await engine().search(searchType, browse(searchType));
@@ -220,6 +267,16 @@ function mismatchedFilter(field, operator) {
220
267
  ? filterOn({ field, range: { min: 0 } })
221
268
  : filterOn({ field, in: ['x'] });
222
269
  }
270
+ /** The ids a reconstructed reference value carries – one labelled reference
271
+ * or a list of them – read the way a consumer reads them. */
272
+ function referenceIds(value) {
273
+ const references = Array.isArray(value) ? value : [value];
274
+ return references
275
+ .map((reference) => typeof reference === 'object' && reference !== null && 'id' in reference
276
+ ? String(reference.id)
277
+ : undefined)
278
+ .filter((id) => id !== undefined);
279
+ }
223
280
  /** The locale a query against this type may select (any is contract-valid). */
224
281
  function firstLocale(searchType) {
225
282
  for (const field of searchType.fields) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lde/search",
3
- "version": "0.23.1",
3
+ "version": "0.24.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",
@@ -52,7 +52,7 @@
52
52
  "devDependencies": {
53
53
  "@tpluscode/rdf-ns-builders": "^5.0.0",
54
54
  "@types/jsonld": "^1.5.15",
55
- "n3": "^2.1.2"
55
+ "n3": "^2.4.0"
56
56
  },
57
57
  "peerDependencies": {
58
58
  "vitest": "^4.0.0"