@lde/search 0.23.0 → 0.23.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/adapter.d.ts +1 -1
- package/dist/adapter.d.ts.map +1 -1
- package/dist/adapter.js +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/project.d.ts.map +1 -1
- package/dist/project.js +116 -13
- package/dist/schema.d.ts +180 -7
- package/dist/schema.d.ts.map +1 -1
- package/dist/schema.js +233 -18
- package/dist/testing.d.ts.map +1 -1
- package/dist/testing.js +58 -1
- package/package.json +1 -1
package/dist/adapter.d.ts
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
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, referenceFields, referenceTypeNamed, inlineFramingDepth, fieldNamed, datasetField, ID_FIELD, AND_KEY, OR_KEY, labelFieldOf, labelFieldNameOf, labelSourceNameOf, DEFAULT_LABEL_FIELD, isRangeFacet, isAbsoluteIri, isoToUnixSeconds, unixSecondsToIso, } from './schema.js';
|
|
9
|
+
export { assertTypeInSchema, physicalFields, irAlias, displayFieldName, displayFieldPattern, displayLangOf, searchableFields, facetableFields, filterableFields, sortableFields, outputFields, isInternalField, isInlineReference, nestedReferenceType, nestedFieldName, referenceFields, referenceTypeNamed, rootTypeNamed, inlineFramingDepth, fieldNamed, datasetField, ID_FIELD, AND_KEY, OR_KEY, labelFieldOf, labelFieldNameOf, labelSourceNameOf, documentKeyOf, DEFAULT_LABEL_FIELD, isRangeFacet, 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 { joinGraph, MAX_JOIN_DEPTH } from './join-graph.js';
|
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,eAAe,EACf,kBAAkB,EAClB,kBAAkB,EAClB,UAAU,EACV,YAAY,EACZ,QAAQ,EACR,OAAO,EACP,MAAM,EACN,YAAY,EACZ,gBAAgB,EAChB,iBAAiB,EACjB,mBAAmB,EACnB,YAAY,EACZ,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,SAAS,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AAC5D,YAAY,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AACjD,OAAO,EACL,iBAAiB,EACjB,cAAc,EACd,QAAQ,EACR,eAAe,EACf,aAAa,EACb,gBAAgB,EAChB,aAAa,GACd,MAAM,YAAY,CAAC;AACpB,YAAY,EAAE,cAAc,EAAE,UAAU,EAAE,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,eAAe,EACf,kBAAkB,EAClB,aAAa,EACb,kBAAkB,EAClB,UAAU,EACV,YAAY,EACZ,QAAQ,EACR,OAAO,EACP,MAAM,EACN,YAAY,EACZ,gBAAgB,EAChB,iBAAiB,EACjB,aAAa,EACb,mBAAmB,EACnB,YAAY,EACZ,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,SAAS,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AAC5D,YAAY,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AACjD,OAAO,EACL,iBAAiB,EACjB,cAAc,EACd,QAAQ,EACR,eAAe,EACf,aAAa,EACb,gBAAgB,EAChB,aAAa,GACd,MAAM,YAAY,CAAC;AACpB,YAAY,EAAE,cAAc,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC"}
|
package/dist/adapter.js
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
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, referenceFields, referenceTypeNamed, inlineFramingDepth, fieldNamed, datasetField, ID_FIELD, AND_KEY, OR_KEY, labelFieldOf, labelFieldNameOf, labelSourceNameOf, DEFAULT_LABEL_FIELD, isRangeFacet, isAbsoluteIri, isoToUnixSeconds, unixSecondsToIso, } from './schema.js';
|
|
9
|
+
export { assertTypeInSchema, physicalFields, irAlias, displayFieldName, displayFieldPattern, displayLangOf, searchableFields, facetableFields, filterableFields, sortableFields, outputFields, isInternalField, isInlineReference, nestedReferenceType, nestedFieldName, referenceFields, referenceTypeNamed, rootTypeNamed, inlineFramingDepth, fieldNamed, datasetField, ID_FIELD, AND_KEY, OR_KEY, labelFieldOf, labelFieldNameOf, labelSourceNameOf, documentKeyOf, DEFAULT_LABEL_FIELD, isRangeFacet, isAbsoluteIri, isoToUnixSeconds, unixSecondsToIso, } from './schema.js';
|
|
10
10
|
export { physicalNameTokens } from './physical-name.js';
|
|
11
11
|
export { joinGraph, MAX_JOIN_DEPTH } from './join-graph.js';
|
|
12
12
|
export { filterOperatorFor, filterOperator, filterOn, isUnsatisfiable, validateQuery, assertValidQuery, pageForOffset, } from './query.js';
|
package/dist/index.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
export { projectRoots } from './project.js';
|
|
2
2
|
export type { ProjectedNode, ProjectionContext, SearchDocument, } from './project.js';
|
|
3
3
|
export { defineSearchType, searchSchema, validateSearchType, assertValidSearchType, ID_FIELD, AND_KEY, OR_KEY, isoToUnixSeconds, unixSecondsToIso, } from './schema.js';
|
|
4
|
-
export type { FieldKind, SearchField, SearchFieldBase, TextField, KeywordField, ReferenceField, ReferenceStrategy, NumericField, BooleanField, SearchType, SearchTypeBase, RootType, ReferenceType, RootTypeOf, SearchTypeIssue, SearchSchema, FacetRange, ProjectionValue, } from './schema.js';
|
|
4
|
+
export type { FieldKind, SearchField, SearchFieldBase, TextField, KeywordField, ReferenceField, ReferenceStrategy, NumericField, BooleanField, SearchType, SearchTypeBase, RootType, ReferenceType, RootTypeOf, SearchTypeIssue, KeyField, FacetKeys, SearchSchema, FacetRange, ProjectionValue, } from './schema.js';
|
|
5
5
|
export { joinGraph, MAX_JOIN_DEPTH } from './join-graph.js';
|
|
6
6
|
export type { JoinGraph } from './join-graph.js';
|
|
7
7
|
export { filterOn } from './query.js';
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AASA,OAAO,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAC5C,YAAY,EACV,aAAa,EACb,iBAAiB,EACjB,cAAc,GACf,MAAM,cAAc,CAAC;AAKtB,OAAO,EACL,gBAAgB,EAChB,YAAY,EACZ,kBAAkB,EAClB,qBAAqB,EACrB,QAAQ,EACR,OAAO,EACP,MAAM,EAMN,gBAAgB,EAChB,gBAAgB,GACjB,MAAM,aAAa,CAAC;AACrB,YAAY,EACV,SAAS,EACT,WAAW,EACX,eAAe,EACf,SAAS,EACT,YAAY,EACZ,cAAc,EACd,iBAAiB,EACjB,YAAY,EACZ,YAAY,EACZ,UAAU,EACV,cAAc,EACd,QAAQ,EACR,aAAa,EACb,UAAU,EACV,eAAe,EACf,YAAY,EACZ,UAAU,EACV,eAAe,GAChB,MAAM,aAAa,CAAC;AAKrB,OAAO,EAAE,SAAS,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AAC5D,YAAY,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAOjD,OAAO,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AACtC,YAAY,EACV,WAAW,EACX,mBAAmB,EACnB,MAAM,EACN,SAAS,EACT,aAAa,EACb,IAAI,GACL,MAAM,YAAY,CAAC;AAIpB,YAAY,EACV,YAAY,EACZ,YAAY,EACZ,SAAS,EACT,cAAc,EACd,WAAW,EACX,cAAc,EACd,SAAS,EACT,cAAc,EACd,WAAW,EACX,QAAQ,EACR,aAAa,EACb,aAAa,EACb,cAAc,GACf,MAAM,aAAa,CAAC;AAErB,YAAY,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AASA,OAAO,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAC5C,YAAY,EACV,aAAa,EACb,iBAAiB,EACjB,cAAc,GACf,MAAM,cAAc,CAAC;AAKtB,OAAO,EACL,gBAAgB,EAChB,YAAY,EACZ,kBAAkB,EAClB,qBAAqB,EACrB,QAAQ,EACR,OAAO,EACP,MAAM,EAMN,gBAAgB,EAChB,gBAAgB,GACjB,MAAM,aAAa,CAAC;AACrB,YAAY,EACV,SAAS,EACT,WAAW,EACX,eAAe,EACf,SAAS,EACT,YAAY,EACZ,cAAc,EACd,iBAAiB,EACjB,YAAY,EACZ,YAAY,EACZ,UAAU,EACV,cAAc,EACd,QAAQ,EACR,aAAa,EACb,UAAU,EACV,eAAe,EACf,QAAQ,EACR,SAAS,EACT,YAAY,EACZ,UAAU,EACV,eAAe,GAChB,MAAM,aAAa,CAAC;AAKrB,OAAO,EAAE,SAAS,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AAC5D,YAAY,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAOjD,OAAO,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AACtC,YAAY,EACV,WAAW,EACX,mBAAmB,EACnB,MAAM,EACN,SAAS,EACT,aAAa,EACb,IAAI,GACL,MAAM,YAAY,CAAC;AAIpB,YAAY,EACV,YAAY,EACZ,YAAY,EACZ,SAAS,EACT,cAAc,EACd,WAAW,EACX,cAAc,EACd,SAAS,EACT,cAAc,EACd,WAAW,EACX,QAAQ,EACR,aAAa,EACb,aAAa,EACb,cAAc,GACf,MAAM,aAAa,CAAC;AAErB,YAAY,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAC"}
|
package/dist/project.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"project.d.ts","sourceRoot":"","sources":["../src/project.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,cAAc,CAAC;AAEzC,OAAO,EAGL,KAAK,UAAU,EAChB,MAAM,oBAAoB,CAAC;AAC5B,OAAO,
|
|
1
|
+
{"version":3,"file":"project.d.ts","sourceRoot":"","sources":["../src/project.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,cAAc,CAAC;AAEzC,OAAO,EAGL,KAAK,UAAU,EAChB,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EAiBL,KAAK,eAAe,EAEpB,KAAK,QAAQ,EAEb,KAAK,YAAY,EACjB,KAAK,UAAU,EAEhB,MAAM,aAAa,CAAC;AAErB;;;;;;GAMG;AACH,MAAM,MAAM,aAAa,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;AAEpD,+DAA+D;AAC/D,MAAM,MAAM,cAAc,GAAG;IAAE,EAAE,EAAE,MAAM,CAAA;CAAE,GAAG,aAAa,CAAC;AAE5D;;;;;;;;;;GAUG;AACH,MAAM,WAAW,iBAAkB,SAAQ,OAAO,CAChD,QAAQ,CAAC,MAAM,CAAC,eAAe,EAAE,MAAM,CAAC,CAAC,CAC1C;IACC,4CAA4C;IAC5C,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAC;CAC3B;AAKD;;;;;;;;;;;;;GAaG;AACH,wBAAgB,eAAe,CAC7B,IAAI,EAAE,UAAU,EAChB,UAAU,EAAE,UAAU,EACtB,MAAM,CAAC,EAAE,YAAY,EACrB,OAAO,GAAE,iBAAyC,GACjD,cAAc,CAgBhB;AAmHD;;;;;;;;;;;;GAYG;AACH,wBAAuB,YAAY,CACjC,KAAK,EAAE,QAAQ,CAAC,IAAI,CAAC,EACrB,KAAK,EAAE,SAAS,MAAM,EAAE,EACxB,MAAM,EAAE,YAAY,EACpB,UAAU,EAAE,QAAQ,EACpB,OAAO,GAAE,iBAAyC,GACjD,aAAa,CAAC,cAAc,CAAC,CAc/B"}
|
package/dist/project.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { fold } from '@lde/text-normalization';
|
|
2
2
|
import { buildSubjectIndex, frameSubjects, } from './frame-by-type.js';
|
|
3
|
-
import { assertTypeInSchema, displayFieldName, inlineFramingDepth, irAlias, isAbsoluteIri, isInternalField, isInlineReference, isoToUnixSeconds, physicalFields, referenceTypeNamed, } from './schema.js';
|
|
3
|
+
import { assertTypeInSchema, displayFieldName, documentKeyOf, fieldNamed, inheritedFacetKeys, inlineFramingDepth, irAlias, isAbsoluteIri, isInternalField, isInlineReference, isoToUnixSeconds, labelSourceNameOf, physicalFields, referenceTypeNamed, rootTypeNamed, } from './schema.js';
|
|
4
4
|
/** The context a projection runs with when its caller supplied none. */
|
|
5
5
|
const NO_PROJECTION_CONTEXT = {};
|
|
6
6
|
/**
|
|
@@ -68,7 +68,7 @@ function pruneInternalFields(document, searchType, schema) {
|
|
|
68
68
|
* once the whole nested structure is projected, so a derive at any depth can
|
|
69
69
|
* still read an internal field before it is removed. */
|
|
70
70
|
function projectFields(node, searchType, schema, context) {
|
|
71
|
-
const id =
|
|
71
|
+
const id = documentIdOf(node, searchType);
|
|
72
72
|
const document = id === undefined ? {} : { id };
|
|
73
73
|
for (const field of searchType.fields) {
|
|
74
74
|
applyField(document, node, field, searchType, schema, context);
|
|
@@ -88,6 +88,33 @@ function documentKey(node) {
|
|
|
88
88
|
const id = node['@id'];
|
|
89
89
|
return typeof id === 'string' && isAbsoluteIri(id) ? id : undefined;
|
|
90
90
|
}
|
|
91
|
+
/**
|
|
92
|
+
* The `id` a node is projected under: its own IRI, unless its type declares a
|
|
93
|
+
* {@link RootType.key} – then the key its key field carries
|
|
94
|
+
* ({@link documentKeyOf}). The candidates are read off the frame like any other
|
|
95
|
+
* field value, under the key field’s own {@link irAlias IR Alias}, because the
|
|
96
|
+
* key field *is* an ordinary declared field: the extraction already emits it,
|
|
97
|
+
* and a reader transform that repairs reference values has already run on it.
|
|
98
|
+
*
|
|
99
|
+
* The key is assigned before any `derive` runs, so a derive sees the key and
|
|
100
|
+
* never the node IRI; a deployment that wants the node IRI declares a plain
|
|
101
|
+
* `idOnly` reference over the same path.
|
|
102
|
+
*/
|
|
103
|
+
function documentIdOf(node, searchType) {
|
|
104
|
+
const nodeIri = documentKey(node);
|
|
105
|
+
if (nodeIri === undefined || searchType.key === undefined) {
|
|
106
|
+
return nodeIri;
|
|
107
|
+
}
|
|
108
|
+
return documentKeyOf(searchType, nodeIri, keyCandidatesOf(node, searchType, searchType.key.field));
|
|
109
|
+
}
|
|
110
|
+
/** The raw values a node carries under a type’s key field – untransformed, as
|
|
111
|
+
* {@link documentKeyOf} expects them. Empty for a node the key field matched
|
|
112
|
+
* nothing on. The field itself is guaranteed declared by `searchSchema`, which
|
|
113
|
+
* is what validates a `key` at all. */
|
|
114
|
+
function keyCandidatesOf(node, searchType, name) {
|
|
115
|
+
const keyField = fieldNamed(searchType, name);
|
|
116
|
+
return irisOf(node, irAlias(searchType, keyField));
|
|
117
|
+
}
|
|
91
118
|
/**
|
|
92
119
|
* Project a single type over a known set of `roots` – the per-type, roots-given
|
|
93
120
|
* projection. The roots are supplied by the caller (the pipeline selector)
|
|
@@ -106,8 +133,11 @@ export async function* projectRoots(quads, roots, schema, searchType, context =
|
|
|
106
133
|
const index = buildSubjectIndex(quads);
|
|
107
134
|
// Distinct roots only. A selector may return an IRI more than once – a
|
|
108
135
|
// non-`DISTINCT` `SELECT` over a one-to-many join yields the same subject per
|
|
109
|
-
// matched row – and a
|
|
110
|
-
//
|
|
136
|
+
// matched row – and framing a root twice would do the same work twice for one
|
|
137
|
+
// node. Distinct roots may still legitimately share an `id`, where the type
|
|
138
|
+
// declares a `key` two of them carry: that is what a document key means, and
|
|
139
|
+
// it is the writer’s upsert – not the projection – that folds them into one
|
|
140
|
+
// document.
|
|
111
141
|
const depth = inlineFramingDepth(schema, searchType);
|
|
112
142
|
for await (const node of frameSubjects(index, [...new Set(roots)], depth)) {
|
|
113
143
|
yield projectDocument(node, searchType, schema, context);
|
|
@@ -118,7 +148,7 @@ function applyField(document, node, field, searchType, schema, context) {
|
|
|
118
148
|
// (`validateSearchType`): a projection value, a computed value, a graph path.
|
|
119
149
|
if ((field.kind === 'keyword' || field.kind === 'reference') &&
|
|
120
150
|
field.from !== undefined) {
|
|
121
|
-
return applyProjectionValue(document, field, field.from, context);
|
|
151
|
+
return applyProjectionValue(document, field, field.from, context, schema);
|
|
122
152
|
}
|
|
123
153
|
if (field.derive !== undefined) {
|
|
124
154
|
const derived = field.derive(document, context);
|
|
@@ -177,9 +207,9 @@ function applyField(document, node, field, searchType, schema, context) {
|
|
|
177
207
|
case 'text':
|
|
178
208
|
return applyText(document, langValuesOf(node, alias), field);
|
|
179
209
|
case 'keyword':
|
|
180
|
-
return applyFacet(document, literalsOf(node, alias), field);
|
|
210
|
+
return applyFacet(document, literalsOf(node, alias), field, schema);
|
|
181
211
|
case 'reference':
|
|
182
|
-
return applyFacet(document,
|
|
212
|
+
return applyFacet(document, referenceValues(node, alias, field, schema), field, schema);
|
|
183
213
|
case 'integer':
|
|
184
214
|
return setNumber(document, field.name, toInteger(firstLiteralOf(node, alias)));
|
|
185
215
|
case 'number':
|
|
@@ -198,6 +228,53 @@ function applyField(document, node, field, searchType, schema, context) {
|
|
|
198
228
|
}
|
|
199
229
|
}
|
|
200
230
|
}
|
|
231
|
+
/**
|
|
232
|
+
* The values a non-inline reference stores: the referents’ IRIs, **or their
|
|
233
|
+
* document keys** when the type the reference names declares a
|
|
234
|
+
* {@link RootType.key}. A `lookup`’s `target` and an `idOnly`’s `labelSource`
|
|
235
|
+
* both mean *this field holds ids of documents in that collection* (ADR 20) –
|
|
236
|
+
* the fact a label lookup and a join rely on – so storing the node IRI where
|
|
237
|
+
* the target keys on something else would break an invariant the schema already
|
|
238
|
+
* has. Rewriting a reference is LDE keeping it, not a new rule.
|
|
239
|
+
*
|
|
240
|
+
* That is also why the boundary is *naming the target*: a reference that names
|
|
241
|
+
* none ({@link labelSourceNameOf} → `undefined`) never claimed to hold a
|
|
242
|
+
* collection’s ids, and a `derive`d one produces its own values rather than
|
|
243
|
+
* reading a referent, so neither is re-keyed.
|
|
244
|
+
*
|
|
245
|
+
* The referent’s candidates are in the frame because framing already embeds a
|
|
246
|
+
* referenced node’s own triples at one hop, and the extraction adds the key
|
|
247
|
+
* field to that hop. A projection run without a `schema` cannot resolve the
|
|
248
|
+
* target, so it leaves the values as they are – exactly as it leaves an inline
|
|
249
|
+
* reference unprojected.
|
|
250
|
+
*
|
|
251
|
+
* The referring field’s own {@link ReferenceField.transform} still runs after
|
|
252
|
+
* this, in {@link applyFacet}: a `transform` transforms what the field stores,
|
|
253
|
+
* and for a keyed target that is the key. A `transform` written to repair the
|
|
254
|
+
* referent’s node IRIs therefore sees a key instead, which is why the two are
|
|
255
|
+
* worth declaring together only deliberately.
|
|
256
|
+
*/
|
|
257
|
+
function referenceValues(node, alias, field, schema) {
|
|
258
|
+
const targetName = labelSourceNameOf(field);
|
|
259
|
+
if (schema === undefined || targetName === undefined) {
|
|
260
|
+
return irisOf(node, alias);
|
|
261
|
+
}
|
|
262
|
+
// Guaranteed declared: `searchSchema` validates that every named target
|
|
263
|
+
// resolves, and the projection only ever runs against a type of its schema.
|
|
264
|
+
const target = rootTypeNamed(schema, targetName);
|
|
265
|
+
if (target.key === undefined) {
|
|
266
|
+
return irisOf(node, alias);
|
|
267
|
+
}
|
|
268
|
+
const keyFieldName = target.key.field;
|
|
269
|
+
return valuesOf(node, alias)
|
|
270
|
+
.map((value) => {
|
|
271
|
+
const iri = iriString(value);
|
|
272
|
+
return iri === undefined
|
|
273
|
+
? undefined
|
|
274
|
+
: documentKeyOf(target, iri, isObject(value) ? keyCandidatesOf(value, target, keyFieldName) : []);
|
|
275
|
+
})
|
|
276
|
+
.filter((value) => value !== undefined);
|
|
277
|
+
}
|
|
201
278
|
/**
|
|
202
279
|
* Project a text field. **Display** (when `output`) preserves *every* language
|
|
203
280
|
* present – one label per language (accents preserved, untagged under `und`),
|
|
@@ -250,6 +327,16 @@ function foldedSearchValue(values) {
|
|
|
250
327
|
* `keyword` reads literals; `reference` reads IRIs (the caller passes the
|
|
251
328
|
* already-read raw values).
|
|
252
329
|
*
|
|
330
|
+
* A reference inheriting a {@link FacetKeys facet policy} from the type it
|
|
331
|
+
* names ({@link inheritedFacetKeys}) also writes the `${name}_facet` companion
|
|
332
|
+
* the engine facets instead of the field: the subset of **what the field
|
|
333
|
+
* stores** that the policy admits. So it is written after the field’s own
|
|
334
|
+
* `transform` and the IRI filter, from the same `values` – for a keyed target
|
|
335
|
+
* those are already keys ({@link referenceValues}), which is what makes a
|
|
336
|
+
* predicate over document keys the right shape. The field itself keeps every
|
|
337
|
+
* value; only the facet narrows. An empty subset writes nothing, exactly as a
|
|
338
|
+
* path that matched nothing does.
|
|
339
|
+
*
|
|
253
340
|
* **`array` decides the shape**, as it does for every other kind: a declared
|
|
254
341
|
* `array` field stores a list, and a single-valued one stores the first value –
|
|
255
342
|
* the graph may still carry several, exactly as it may carry several literals
|
|
@@ -260,7 +347,7 @@ function foldedSearchValue(values) {
|
|
|
260
347
|
* engine rejects, or – where it does not type-check, as inside a nested
|
|
261
348
|
* document – a value the API cannot serialize.
|
|
262
349
|
*/
|
|
263
|
-
function applyFacet(document, raw, field) {
|
|
350
|
+
function applyFacet(document, raw, field, schema) {
|
|
264
351
|
// A `reference` stores identity, so what it stores must be an IRI whatever
|
|
265
352
|
// route the value arrived by. {@link iriString} guards the graph path, but a
|
|
266
353
|
// `transform` runs after it and a `from` projection value never passes it at
|
|
@@ -270,18 +357,34 @@ function applyFacet(document, raw, field) {
|
|
|
270
357
|
const transformed = field.transform ? raw.map(field.transform) : raw;
|
|
271
358
|
const values = dedupe(referenced ? transformed.filter(isAbsoluteIri) : transformed);
|
|
272
359
|
const folded = dedupe(values.map((value) => fold(value)));
|
|
273
|
-
const
|
|
360
|
+
const names = physicalFields(field, schema);
|
|
361
|
+
const searchField = names.search[0];
|
|
362
|
+
const policy = inheritedFacetKeys(field, schema);
|
|
363
|
+
// `names.facet` is the companion exactly when a policy is inherited –
|
|
364
|
+
// physicalFields reads the same `inheritedFacetKeys`.
|
|
365
|
+
const facetField = names.facet;
|
|
366
|
+
// The companion is a subset of what the field STORES – for a single-valued
|
|
367
|
+
// field its first value, not the first admitted of all of them, or the facet
|
|
368
|
+
// would count a value no filter on the field can reproduce.
|
|
369
|
+
const stored = field.array === true ? values : values.slice(0, 1);
|
|
370
|
+
const admitted = policy === undefined ? [] : stored.filter(policy.only);
|
|
274
371
|
if (field.array === true) {
|
|
275
|
-
setArray(document, field.name,
|
|
372
|
+
setArray(document, field.name, stored);
|
|
276
373
|
if (field.searchable) {
|
|
277
374
|
setArray(document, searchField, folded);
|
|
278
375
|
}
|
|
376
|
+
if (policy !== undefined) {
|
|
377
|
+
setArray(document, facetField, admitted);
|
|
378
|
+
}
|
|
279
379
|
return;
|
|
280
380
|
}
|
|
281
|
-
setString(document, field.name,
|
|
381
|
+
setString(document, field.name, stored[0]);
|
|
282
382
|
if (field.searchable) {
|
|
283
383
|
setString(document, searchField, folded[0]);
|
|
284
384
|
}
|
|
385
|
+
if (policy !== undefined) {
|
|
386
|
+
setString(document, facetField, admitted[0]);
|
|
387
|
+
}
|
|
285
388
|
}
|
|
286
389
|
/**
|
|
287
390
|
* Project a field declared over a {@link ProjectionContext} value: read the
|
|
@@ -294,13 +397,13 @@ function applyFacet(document, raw, field) {
|
|
|
294
397
|
* pipeline (a test, a one-off) leaves the field absent rather than inventing a
|
|
295
398
|
* placeholder, the same as a `path` that matched nothing.
|
|
296
399
|
*/
|
|
297
|
-
function applyProjectionValue(document, field, from, context) {
|
|
400
|
+
function applyProjectionValue(document, field, from, context, schema) {
|
|
298
401
|
// `ProjectionContext` is keyed by `ProjectionValue`, so the declaration reads
|
|
299
402
|
// the context directly: a projection value that has no context key, or a
|
|
300
403
|
// context key no declaration can name, is a compile error rather than a
|
|
301
404
|
// silently unpopulated field.
|
|
302
405
|
const value = context[from];
|
|
303
|
-
applyFacet(document, value === undefined ? [] : [value], field);
|
|
406
|
+
applyFacet(document, value === undefined ? [] : [value], field, schema);
|
|
304
407
|
}
|
|
305
408
|
/**
|
|
306
409
|
* Project an inline reference: the referent node(s) embedded under the field’s
|
package/dist/schema.d.ts
CHANGED
|
@@ -321,6 +321,90 @@ export interface SearchTypeBase {
|
|
|
321
321
|
readonly labelField?: string;
|
|
322
322
|
readonly fields: readonly SearchField[];
|
|
323
323
|
}
|
|
324
|
+
/**
|
|
325
|
+
* Which declared field of a {@link RootType} holds its **document key**, when
|
|
326
|
+
* that key is not the node’s own IRI. It has the shape of
|
|
327
|
+
* {@link SearchTypeBase.labelField} – *which field is the label* – and says
|
|
328
|
+
* *which field holds the key*: a statement about the search document, never
|
|
329
|
+
* about the world, so the words a deployment reaches for (identity, alignment,
|
|
330
|
+
* canonical, authority) stay in the deployment’s own schema comments.
|
|
331
|
+
*
|
|
332
|
+
* Two things that already hold of keys then hold here too, without a new rule:
|
|
333
|
+
* several nodes sharing a key are **one document** (the writer upserts by `id`,
|
|
334
|
+
* so a deployment that wants particular content on the merged document attaches
|
|
335
|
+
* a transform, and one that does not gets last-writer-wins), and a reference
|
|
336
|
+
* into a keyed type stores the **target’s key** – a `lookup`/`labelSource`
|
|
337
|
+
* already means *this field holds ids of documents in that collection*
|
|
338
|
+
* (ADR 20), which storing the node IRI would break.
|
|
339
|
+
*
|
|
340
|
+
* See [ADR 22](../../docs/decisions/0022-key-a-root-type-on-a-declared-field.md).
|
|
341
|
+
*/
|
|
342
|
+
export interface KeyField {
|
|
343
|
+
/**
|
|
344
|
+
* The `name` of a declared field of this type whose values are the key
|
|
345
|
+
* candidates: a path-bearing, `array` {@link ReferenceField} that is not
|
|
346
|
+
* `inline` ({@link searchSchema} validates all four). Being an ordinary field
|
|
347
|
+
* is the point – its extraction branch already exists, a reader transform
|
|
348
|
+
* that repairs reference values covers it the day it is declared, and its own
|
|
349
|
+
* {@link ReferenceField.transform} is where IRI normalisation lives, so two
|
|
350
|
+
* spellings of one IRI become one candidate before anything chooses among
|
|
351
|
+
* them.
|
|
352
|
+
*
|
|
353
|
+
* A transform that **replaces** a root’s quads must re-emit this field, the
|
|
354
|
+
* existing rule that *a field the document needs must be in the stream*
|
|
355
|
+
* applied to one more field; a transform that only adds never meets it.
|
|
356
|
+
*/
|
|
357
|
+
readonly field: string;
|
|
358
|
+
/**
|
|
359
|
+
* Choose the key among the candidates – the key field’s values after its
|
|
360
|
+
* `transform`, IRI-filtered, deduplicated and sorted, so the default is
|
|
361
|
+
* deterministic whatever order the CONSTRUCT returned them in. Return one of
|
|
362
|
+
* them, or `undefined` to keep the node’s own IRI; anything else throws at
|
|
363
|
+
* projection. Not consulted for a node whose key field is empty.
|
|
364
|
+
*
|
|
365
|
+
* Must be **pure**: the same function keys the document and every reference
|
|
366
|
+
* to it, so a `pick` that consulted the network or the clock could key the
|
|
367
|
+
* two differently and leave a reference dangling. LDE never inspects an
|
|
368
|
+
* IRI’s shape – it asks.
|
|
369
|
+
*
|
|
370
|
+
* @default the first candidate
|
|
371
|
+
*/
|
|
372
|
+
readonly pick?: (candidates: readonly string[]) => string | undefined;
|
|
373
|
+
}
|
|
374
|
+
/**
|
|
375
|
+
* A {@link RootType}’s **facet policy**: which of its documents get a facet
|
|
376
|
+
* bucket, as a predicate over the document key. Declared once, on the type,
|
|
377
|
+
* and inherited by every facetable reference that *names* the type – a
|
|
378
|
+
* `lookup`’s `target`, an `idOnly`’s `labelSource` ({@link labelSourceNameOf})
|
|
379
|
+
* – because *which of a type’s ids deserve a bucket* is a fact about the type,
|
|
380
|
+
* not about each field that points at it. A per-field policy would be one rule
|
|
381
|
+
* declared N times, and forgetting one would silently reintroduce the buckets
|
|
382
|
+
* on that facet alone.
|
|
383
|
+
*
|
|
384
|
+
* Only the facet narrows. The referring field keeps **every** value: a
|
|
385
|
+
* document still displays the excluded referent, and a `where` filter on its
|
|
386
|
+
* IRI still matches exactly. *Facets are discovery, filters are exact.*
|
|
387
|
+
*
|
|
388
|
+
* The mechanism is a second physical field per inheriting facet –
|
|
389
|
+
* `${name}_facet`, holding the admitted subset ({@link physicalFields}) – which
|
|
390
|
+
* the engine facets instead of the field itself, so the facet is exact under
|
|
391
|
+
* any bucket cap: the engine never sees an excluded value. Declaring a policy
|
|
392
|
+
* therefore changes the collection definition of every type that references
|
|
393
|
+
* this one; an engine adapter that reuses a live collection must notice.
|
|
394
|
+
*
|
|
395
|
+
* **The failure mode is silence.** A predicate that admits none of a type’s
|
|
396
|
+
* keys – a type whose alignments are not what it tests for, or stored values
|
|
397
|
+
* that are still node IRIs because the type was keyed after its references
|
|
398
|
+
* were indexed – empties every facet referencing the type without an error.
|
|
399
|
+
* Give each type its own predicate rather than reuse another’s, and key a type
|
|
400
|
+
* ({@link RootType.key}) before declaring a policy over its keys.
|
|
401
|
+
*/
|
|
402
|
+
export interface FacetKeys {
|
|
403
|
+
/** Whether a document key gets a bucket. Must be **pure**: it is applied per
|
|
404
|
+
* document at projection time, so an impure one would bucket one document
|
|
405
|
+
* and not its twin. */
|
|
406
|
+
readonly only: (id: string) => boolean;
|
|
407
|
+
}
|
|
324
408
|
/**
|
|
325
409
|
* A **Root Type**: a {@link SearchType} that is indexed. It declares a `class`,
|
|
326
410
|
* roots are selected for it, a Writer owns a collection for it, and the
|
|
@@ -333,6 +417,13 @@ export interface RootType extends SearchTypeBase {
|
|
|
333
417
|
* key a {@link SearchSchema} maps this type under. Its presence is what makes
|
|
334
418
|
* a type a Root Type – and so what gives it a collection. */
|
|
335
419
|
readonly class: string;
|
|
420
|
+
/** Which declared field holds this type’s document key, when it is not the
|
|
421
|
+
* node’s own IRI ({@link KeyField}, {@link documentKeyOf}). */
|
|
422
|
+
readonly key?: KeyField;
|
|
423
|
+
/** Which of this type’s documents get a facet bucket, on every facetable
|
|
424
|
+
* reference that names this type ({@link FacetKeys}). Unset, every
|
|
425
|
+
* document does. */
|
|
426
|
+
readonly facetKeys?: FacetKeys;
|
|
336
427
|
}
|
|
337
428
|
/**
|
|
338
429
|
* A **Reference Type**: a {@link SearchType} reached only through an
|
|
@@ -350,6 +441,13 @@ export interface ReferenceType extends SearchTypeBase {
|
|
|
350
441
|
* {@link SearchField} discriminates by `kind`: an indexed Reference Type
|
|
351
442
|
* fails to compile, not at run time. */
|
|
352
443
|
readonly class?: never;
|
|
444
|
+
/** A Reference Type has no document key to declare: it is nested inside its
|
|
445
|
+
* referrer rather than keyed in a collection of its own. `never` for the
|
|
446
|
+
* same reason `class` is. */
|
|
447
|
+
readonly key?: never;
|
|
448
|
+
/** Nothing references a Reference Type by id, so there is no facet for a
|
|
449
|
+
* policy to narrow. `never` for the same reason `key` is. */
|
|
450
|
+
readonly facetKeys?: never;
|
|
353
451
|
}
|
|
354
452
|
/**
|
|
355
453
|
* One type’s complete search declaration: its logical API `name`, the queryable
|
|
@@ -420,6 +518,19 @@ export declare function searchSchema<const Types extends readonly SearchType[]>(
|
|
|
420
518
|
* resolves an inline reference’s referent shape through it.
|
|
421
519
|
*/
|
|
422
520
|
export declare function referenceTypeNamed(schema: SearchSchema, name: string): ReferenceType | undefined;
|
|
521
|
+
/**
|
|
522
|
+
* The {@link RootType} a declaration names – a `lookup`’s `target`, an
|
|
523
|
+
* `idOnly`’s {@link ReferenceField.labelSource} ({@link labelSourceNameOf}), a
|
|
524
|
+
* join edge – or `undefined` when the schema declares no Root Type by that
|
|
525
|
+
* name. The one reading of *which type does this point at*, so the projection
|
|
526
|
+
* (which re-keys a reference through its target’s {@link KeyField}), the
|
|
527
|
+
* label-source validation and an adapter’s lookup cannot resolve a target
|
|
528
|
+
* differently.
|
|
529
|
+
*
|
|
530
|
+
* Complements {@link referenceTypeNamed}: the two name indexes are disjoint,
|
|
531
|
+
* because {@link searchSchema} rejects a name declared twice.
|
|
532
|
+
*/
|
|
533
|
+
export declare function rootTypeNamed(schema: SearchSchema, name: string): RootType | undefined;
|
|
423
534
|
/** Whether a field is an inline reference – a {@link ReferenceField} whose
|
|
424
535
|
* `ref` carries its referent’s projected fields ({@link ReferenceType}). */
|
|
425
536
|
export declare function isInlineReference(field: SearchField): field is ReferenceField & {
|
|
@@ -495,7 +606,7 @@ export declare function labelSourceNameOf(field: ReferenceField): string | undef
|
|
|
495
606
|
*/
|
|
496
607
|
export interface SearchTypeIssue {
|
|
497
608
|
readonly field: string;
|
|
498
|
-
readonly reason: 'duplicate-field-name' | 'invalid-field-name' | 'unknown-kind' | 'invalid-locale' | 'missing-ref' | 'missing-ref-type-name' | 'ref-not-allowed' | 'text-requires-locales' | 'locales-not-allowed' | 'facet-ranges-not-allowed' | 'searchable-not-allowed' | 'transform-not-allowed' | 'derive-with-path' | 'from-not-allowed' | 'from-with-path' | 'from-with-derive' | 'from-with-inline-ref' | 'unknown-projection-value' | 'duplicate-projection-value' | 'text-not-filterable' | 'text-not-facetable' | 'joinable-not-allowed' | 'joinable-without-label-source' | 'joinable-with-inline-ref' | 'reserved-field-name';
|
|
609
|
+
readonly reason: 'duplicate-field-name' | 'invalid-field-name' | 'unknown-kind' | 'invalid-locale' | 'missing-ref' | 'missing-ref-type-name' | 'ref-not-allowed' | 'text-requires-locales' | 'locales-not-allowed' | 'facet-ranges-not-allowed' | 'searchable-not-allowed' | 'transform-not-allowed' | 'derive-with-path' | 'from-not-allowed' | 'from-with-path' | 'from-with-derive' | 'from-with-inline-ref' | 'unknown-projection-value' | 'duplicate-projection-value' | 'text-not-filterable' | 'text-not-facetable' | 'joinable-not-allowed' | 'joinable-without-label-source' | 'joinable-with-inline-ref' | 'reserved-field-name' | 'key-field-unknown' | 'key-field-not-reference' | 'key-field-without-path' | 'key-field-not-array' | 'key-field-inline' | 'key-pick-not-a-function' | 'key-not-allowed' | 'facet-keys-only-not-a-function' | 'facet-keys-not-allowed';
|
|
499
610
|
}
|
|
500
611
|
/**
|
|
501
612
|
* The reserved logical name of a document’s IRI – the one field every indexed
|
|
@@ -565,7 +676,13 @@ export declare const OR_KEY = "or";
|
|
|
565
676
|
* field has exactly one), is not an `inline` reference (a projection value is
|
|
566
677
|
* a bare IRI, with no referent to carry fields from), and no two fields
|
|
567
678
|
* declare the same projection value – which would leave a consumer reading
|
|
568
|
-
* the dataset off a declaration no rule picks between
|
|
679
|
+
* the dataset off a declaration no rule picks between;
|
|
680
|
+
* - a {@link RootType.key} names a declared, `path`-bearing, `array`,
|
|
681
|
+
* non-`inline` `reference` field of this type, and its `pick` is a function
|
|
682
|
+
* ({@link keyIssues}); a {@link ReferenceType} declares no key at all;
|
|
683
|
+
* - a {@link RootType.facetKeys} policy’s `only` is a function
|
|
684
|
+
* ({@link facetKeysIssues}); a {@link ReferenceType} declares no policy at
|
|
685
|
+
* all.
|
|
569
686
|
*
|
|
570
687
|
* Pure and total: returns every issue rather than throwing;
|
|
571
688
|
* {@link assertValidSearchType} is the throwing entry point.
|
|
@@ -594,6 +711,11 @@ export interface PhysicalFields {
|
|
|
594
711
|
/** Per-locale folded sort keys `${name}_sort_${locale}` (localized text,
|
|
595
712
|
* `sortable`); a non-localized field sorts on its own `name` field. */
|
|
596
713
|
readonly sort: readonly string[];
|
|
714
|
+
/** The field a facet on this field reads, when `facetable`: the field’s own
|
|
715
|
+
* `name`, or the `${name}_facet` companion holding the subset its target’s
|
|
716
|
+
* {@link FacetKeys facet policy} admits. `undefined` for a field that is
|
|
717
|
+
* not facetable. */
|
|
718
|
+
readonly facet: string | undefined;
|
|
597
719
|
}
|
|
598
720
|
/**
|
|
599
721
|
* The display fields of a localized `text` field are pattern-based, not
|
|
@@ -688,6 +810,38 @@ export declare function referenceFields(searchType: SearchType): readonly Refere
|
|
|
688
810
|
export declare function datasetField(searchType: SearchType): (KeywordField | ReferenceField) | undefined;
|
|
689
811
|
/** Look up a field by its logical name. */
|
|
690
812
|
export declare function fieldNamed(searchType: SearchType, name: string): SearchField | undefined;
|
|
813
|
+
/**
|
|
814
|
+
* The **document key** of one node of `searchType`: what the projection writes
|
|
815
|
+
* as the document’s `id`, and what a reference into this type stores.
|
|
816
|
+
*
|
|
817
|
+
* The whole rule, in one place, so the projection and any transform that needs
|
|
818
|
+
* to know a node’s key read the same answer:
|
|
819
|
+
*
|
|
820
|
+
* 1. a type declaring no {@link RootType.key} keys on `nodeIri` – the node’s own
|
|
821
|
+
* IRI, as every type does today;
|
|
822
|
+
* 2. otherwise the key field’s `rawValues` become **candidates**: its
|
|
823
|
+
* {@link ReferenceField.transform} (where IRI normalisation lives, so two
|
|
824
|
+
* spellings of one IRI become one candidate), then the
|
|
825
|
+
* {@link isAbsoluteIri} filter, then dedupe and sort – so the default is
|
|
826
|
+
* deterministic whatever order the CONSTRUCT returned them in;
|
|
827
|
+
* 3. no candidate keys on `nodeIri`, without consulting {@link KeyField.pick};
|
|
828
|
+
* 4. otherwise {@link KeyField.pick} chooses (defaulting to the first
|
|
829
|
+
* candidate), and `undefined` keeps `nodeIri`.
|
|
830
|
+
*
|
|
831
|
+
* So a key is always either the node’s own IRI or an IRI the graph offered for
|
|
832
|
+
* that node – never one invented in between. A `pick` returning anything else
|
|
833
|
+
* throws, naming the node and the candidates: it is a bug in a pure function
|
|
834
|
+
* that keys both the document and every reference to it, and letting it through
|
|
835
|
+
* would key those two differently.
|
|
836
|
+
*
|
|
837
|
+
* @param nodeIri the node’s own IRI – the key it falls back to
|
|
838
|
+
* @param rawValues the key field’s values **as a reference field reads them** –
|
|
839
|
+
* a node’s `@id` or a bare-string value, before the field’s `transform`. A
|
|
840
|
+
* caller that reads them itself (a transform working on quads) must apply the
|
|
841
|
+
* same rule and skip a literal-valued object, or it will compute a key for a
|
|
842
|
+
* value the projection never saw and key the two differently.
|
|
843
|
+
*/
|
|
844
|
+
export declare function documentKeyOf(searchType: RootType, nodeIri: string, rawValues: readonly string[]): string;
|
|
691
845
|
/**
|
|
692
846
|
* The **IR Alias** predicate for a field: `urn:lde:‹SearchType.name›/‹field.name›`.
|
|
693
847
|
* The extraction CONSTRUCT emits a field’s value under this minted predicate, and
|
|
@@ -746,9 +900,28 @@ export declare function isoToUnixSeconds(iso: string): number | undefined;
|
|
|
746
900
|
export declare function unixSecondsToIso(seconds: number): string;
|
|
747
901
|
/**
|
|
748
902
|
* Derive the **indexed** physical engine field names a declaration produces:
|
|
749
|
-
* the per-locale `search`/`sort` fanout
|
|
750
|
-
* fields are pattern-based and not enumerated
|
|
751
|
-
* and its siblings.
|
|
752
|
-
|
|
753
|
-
|
|
903
|
+
* the per-locale `search`/`sort` fanout, and the field a `facet` reads. A
|
|
904
|
+
* localized `text` field’s display fields are pattern-based and not enumerated
|
|
905
|
+
* here – see {@link displayFieldName} and its siblings.
|
|
906
|
+
*
|
|
907
|
+
* The facet is the one member that depends on more than the declaration: a
|
|
908
|
+
* facetable reference whose target declares a {@link FacetKeys facet policy}
|
|
909
|
+
* facets a `${name}_facet` companion rather than itself, and only the `schema`
|
|
910
|
+
* can resolve that target ({@link inheritedFacetKeys}). Without one the field
|
|
911
|
+
* facets on its own name – the same reading the projection makes without a
|
|
912
|
+
* schema, where it cannot re-key a reference either.
|
|
913
|
+
*/
|
|
914
|
+
export declare function physicalFields(field: SearchField, schema?: SearchSchema): PhysicalFields;
|
|
915
|
+
/**
|
|
916
|
+
* The {@link FacetKeys facet policy} a field inherits: the `facetKeys` of the
|
|
917
|
+
* Root Type it names ({@link labelSourceNameOf}), when the field is a facetable
|
|
918
|
+
* reference and the schema resolves that type. The boundary is *naming the
|
|
919
|
+
* target* – the same line along which a reference is re-keyed and a join is
|
|
920
|
+
* drawn – so a reference that names no type inherits nothing, whatever type
|
|
921
|
+
* its values happen to point at; and a `derive`d reference, which produces its
|
|
922
|
+
* own values rather than reading a referent, is re-keyed by nothing and so
|
|
923
|
+
* narrowed by nothing either. The one reading the projection (which writes the
|
|
924
|
+
* companion) and {@link physicalFields} (which names it) share.
|
|
925
|
+
*/
|
|
926
|
+
export declare function inheritedFacetKeys(field: SearchField, schema: SearchSchema | undefined): FacetKeys | undefined;
|
|
754
927
|
//# sourceMappingURL=schema.d.ts.map
|
package/dist/schema.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"schema.d.ts","sourceRoot":"","sources":["../src/schema.ts"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"schema.d.ts","sourceRoot":"","sources":["../src/schema.ts"],"names":[],"mappings":"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"}
|
package/dist/schema.js
CHANGED
|
@@ -36,6 +36,17 @@ export function defineSearchType(searchType) {
|
|
|
36
36
|
* through {@link referenceTypeNamed}.
|
|
37
37
|
*/
|
|
38
38
|
const referenceTypesBySchema = new WeakMap();
|
|
39
|
+
/**
|
|
40
|
+
* The Root Type name index each {@link SearchSchema} carries alongside its
|
|
41
|
+
* class-keyed map. A schema is keyed by `class` because that is what a selector
|
|
42
|
+
* and a Writer address it by, while every declaration that points at another
|
|
43
|
+
* type – a `lookup`’s `target`, an `idOnly`’s `labelSource`, a join edge –
|
|
44
|
+
* names it. Kept beside the map rather than rebuilt per consumer, so the
|
|
45
|
+
* projection, the label-source validation and an adapter’s lookup resolve a
|
|
46
|
+
* target the same way ({@link rootTypeNamed}) instead of each maintaining a
|
|
47
|
+
* by-name map of its own.
|
|
48
|
+
*/
|
|
49
|
+
const rootTypesBySchema = new WeakMap();
|
|
39
50
|
/** Whether a declared type is a {@link RootType} (declares a `class`) rather
|
|
40
51
|
* than a {@link ReferenceType}. */
|
|
41
52
|
function isRootType(searchType) {
|
|
@@ -80,12 +91,16 @@ export function searchSchema(...types) {
|
|
|
80
91
|
.map((searchType) => [searchType.name, searchType]));
|
|
81
92
|
assertResolvableInlineReferences(types, referenceTypes);
|
|
82
93
|
assertServiceableNestedFields(referenceTypes);
|
|
83
|
-
|
|
84
|
-
//
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
94
|
+
// The one blessed cast: only this validated constructor mints the brand. Built
|
|
95
|
+
// BEFORE the schema-wide assertions that resolve a type by name, so they can
|
|
96
|
+
// use `rootTypeNamed` – the same reading the projection and an adapter's
|
|
97
|
+
// lookup use – rather than a by-name map of their own. The schema stays local
|
|
98
|
+
// until every assertion has passed, so an invalid one is never observable.
|
|
99
|
+
const rootTypes = types.filter(isRootType);
|
|
100
|
+
const schema = new Map(rootTypes.map((searchType) => [searchType.class, searchType]));
|
|
88
101
|
referenceTypesBySchema.set(schema, referenceTypes);
|
|
102
|
+
rootTypesBySchema.set(schema, new Map(rootTypes.map((searchType) => [searchType.name, searchType])));
|
|
103
|
+
assertResolvableLabelSources(types, schema);
|
|
89
104
|
// Build the join graph eagerly and discard it: it is cached per schema, and
|
|
90
105
|
// building it is what enforces the schema-wide join rules (one joinable
|
|
91
106
|
// reference per target, every target an indexed Root Type, no cycles). A
|
|
@@ -103,6 +118,21 @@ export function searchSchema(...types) {
|
|
|
103
118
|
export function referenceTypeNamed(schema, name) {
|
|
104
119
|
return referenceTypesBySchema.get(schema)?.get(name);
|
|
105
120
|
}
|
|
121
|
+
/**
|
|
122
|
+
* The {@link RootType} a declaration names – a `lookup`’s `target`, an
|
|
123
|
+
* `idOnly`’s {@link ReferenceField.labelSource} ({@link labelSourceNameOf}), a
|
|
124
|
+
* join edge – or `undefined` when the schema declares no Root Type by that
|
|
125
|
+
* name. The one reading of *which type does this point at*, so the projection
|
|
126
|
+
* (which re-keys a reference through its target’s {@link KeyField}), the
|
|
127
|
+
* label-source validation and an adapter’s lookup cannot resolve a target
|
|
128
|
+
* differently.
|
|
129
|
+
*
|
|
130
|
+
* Complements {@link referenceTypeNamed}: the two name indexes are disjoint,
|
|
131
|
+
* because {@link searchSchema} rejects a name declared twice.
|
|
132
|
+
*/
|
|
133
|
+
export function rootTypeNamed(schema, name) {
|
|
134
|
+
return rootTypesBySchema.get(schema)?.get(name);
|
|
135
|
+
}
|
|
106
136
|
/** Whether a field is an inline reference – a {@link ReferenceField} whose
|
|
107
137
|
* `ref` carries its referent’s projected fields ({@link ReferenceType}). */
|
|
108
138
|
export function isInlineReference(field) {
|
|
@@ -311,13 +341,13 @@ export function labelSourceNameOf(field) {
|
|
|
311
341
|
* can actually serve labels ({@link labelFieldOf}). Checked schema-wide,
|
|
312
342
|
* because a single declaration cannot see its siblings.
|
|
313
343
|
*
|
|
314
|
-
*
|
|
315
|
-
*
|
|
316
|
-
*
|
|
317
|
-
*
|
|
344
|
+
* Resolved through {@link rootTypeNamed}, which is exactly the set that can
|
|
345
|
+
* serve labels: a label field is `searchable`, and
|
|
346
|
+
* {@link assertServiceableNestedFields} – which runs first – already rejects a
|
|
347
|
+
* `searchable` field on a Reference Type, so a resolved label always has a
|
|
348
|
+
* collection to come from.
|
|
318
349
|
*/
|
|
319
|
-
function assertResolvableLabelSources(types) {
|
|
320
|
-
const byName = new Map(types.map((searchType) => [searchType.name, searchType]));
|
|
350
|
+
function assertResolvableLabelSources(types, schema) {
|
|
321
351
|
for (const searchType of types) {
|
|
322
352
|
for (const field of searchType.fields) {
|
|
323
353
|
const labelSource = field
|
|
@@ -339,9 +369,15 @@ function assertResolvableLabelSources(types) {
|
|
|
339
369
|
if (sourceName === undefined) {
|
|
340
370
|
continue;
|
|
341
371
|
}
|
|
342
|
-
const source =
|
|
372
|
+
const source = rootTypeNamed(schema, sourceName);
|
|
343
373
|
if (source === undefined) {
|
|
344
|
-
|
|
374
|
+
// A name that IS declared, just not as a Root Type, is the confusing
|
|
375
|
+
// case: telling the author to declare a type they already declared
|
|
376
|
+
// would send them looking in the wrong place. Only a Root Type has a
|
|
377
|
+
// collection to resolve against, so name that instead.
|
|
378
|
+
throw new Error(referenceTypeNamed(schema, sourceName) === undefined
|
|
379
|
+
? `Reference “${searchType.name}.${field.name}” names unknown label source “${sourceName}”; declare a SearchType with that name.`
|
|
380
|
+
: `Reference “${searchType.name}.${field.name}” names label source “${sourceName}”, which is a Reference Type; a label source must be a Root Type, since a resolved label is read from that type’s own collection.`);
|
|
345
381
|
}
|
|
346
382
|
if (labelFieldOf(source) === undefined) {
|
|
347
383
|
throw new Error(`Reference “${searchType.name}.${field.name}” uses label source “${sourceName}”, which must declare an output, searchable text field “${labelFieldNameOf(source)}”.`);
|
|
@@ -449,7 +485,13 @@ const LOCALE_PATTERN = /^[A-Za-z0-9]+(-[A-Za-z0-9]+)*$/;
|
|
|
449
485
|
* field has exactly one), is not an `inline` reference (a projection value is
|
|
450
486
|
* a bare IRI, with no referent to carry fields from), and no two fields
|
|
451
487
|
* declare the same projection value – which would leave a consumer reading
|
|
452
|
-
* the dataset off a declaration no rule picks between
|
|
488
|
+
* the dataset off a declaration no rule picks between;
|
|
489
|
+
* - a {@link RootType.key} names a declared, `path`-bearing, `array`,
|
|
490
|
+
* non-`inline` `reference` field of this type, and its `pick` is a function
|
|
491
|
+
* ({@link keyIssues}); a {@link ReferenceType} declares no key at all;
|
|
492
|
+
* - a {@link RootType.facetKeys} policy’s `only` is a function
|
|
493
|
+
* ({@link facetKeysIssues}); a {@link ReferenceType} declares no policy at
|
|
494
|
+
* all.
|
|
453
495
|
*
|
|
454
496
|
* Pure and total: returns every issue rather than throwing;
|
|
455
497
|
* {@link assertValidSearchType} is the throwing entry point.
|
|
@@ -596,8 +638,84 @@ export function validateSearchType(searchType) {
|
|
|
596
638
|
}
|
|
597
639
|
}
|
|
598
640
|
}
|
|
641
|
+
issues.push(...keyIssues(searchType), ...facetKeysIssues(searchType));
|
|
642
|
+
return issues;
|
|
643
|
+
}
|
|
644
|
+
/**
|
|
645
|
+
* The issues a type’s {@link RootType.key} declaration carries, each filed
|
|
646
|
+
* under the field name the declaration names – so a message reads like every
|
|
647
|
+
* other one, naming the field the rule is about rather than a member.
|
|
648
|
+
*
|
|
649
|
+
* The key field must be an ordinary declared field the extraction already
|
|
650
|
+
* reads and the projection already applies a `transform` to: a `reference`
|
|
651
|
+
* (candidates are IRIs), with a `path` (a key read from the graph, not one
|
|
652
|
+
* derived from a document that is keyed already), `array` (a node may offer
|
|
653
|
+
* several candidates, and a single-valued field would drop all but the first
|
|
654
|
+
* before `pick` ever saw them), and not `inline` (which carries the referent’s
|
|
655
|
+
* fields rather than its IRI, so it offers no candidate at all).
|
|
656
|
+
*/
|
|
657
|
+
function keyIssues(searchType) {
|
|
658
|
+
const key = searchType.key;
|
|
659
|
+
if (key === undefined) {
|
|
660
|
+
return [];
|
|
661
|
+
}
|
|
662
|
+
const issue = (reason) => ({
|
|
663
|
+
field: key.field,
|
|
664
|
+
reason,
|
|
665
|
+
});
|
|
666
|
+
// A Reference Type is nested inside its referrer, never keyed in a collection
|
|
667
|
+
// of its own – so a key on one states a rule nothing could apply.
|
|
668
|
+
if (searchType.class === undefined) {
|
|
669
|
+
return [issue('key-not-allowed')];
|
|
670
|
+
}
|
|
671
|
+
const issues = [];
|
|
672
|
+
if (key.pick !== undefined && typeof key.pick !== 'function') {
|
|
673
|
+
issues.push(issue('key-pick-not-a-function'));
|
|
674
|
+
}
|
|
675
|
+
const field = fieldNamed(searchType, key.field);
|
|
676
|
+
if (field === undefined) {
|
|
677
|
+
issues.push(issue('key-field-unknown'));
|
|
678
|
+
return issues;
|
|
679
|
+
}
|
|
680
|
+
if (field.kind !== 'reference') {
|
|
681
|
+
issues.push(issue('key-field-not-reference'));
|
|
682
|
+
return issues;
|
|
683
|
+
}
|
|
684
|
+
if (field.path === undefined) {
|
|
685
|
+
issues.push(issue('key-field-without-path'));
|
|
686
|
+
}
|
|
687
|
+
if (field.array !== true) {
|
|
688
|
+
issues.push(issue('key-field-not-array'));
|
|
689
|
+
}
|
|
690
|
+
if (field.ref?.strategy === 'inline') {
|
|
691
|
+
issues.push(issue('key-field-inline'));
|
|
692
|
+
}
|
|
599
693
|
return issues;
|
|
600
694
|
}
|
|
695
|
+
/**
|
|
696
|
+
* The issues a type’s {@link RootType.facetKeys} declaration carries, filed
|
|
697
|
+
* under the member’s name: the policy names no field – it is a rule about the
|
|
698
|
+
* type’s own keys, applied through other types’ fields – so there is no field
|
|
699
|
+
* name to file it under.
|
|
700
|
+
*/
|
|
701
|
+
function facetKeysIssues(searchType) {
|
|
702
|
+
const facetKeys = searchType.facetKeys;
|
|
703
|
+
if (facetKeys === undefined) {
|
|
704
|
+
return [];
|
|
705
|
+
}
|
|
706
|
+
const issue = (reason) => ({
|
|
707
|
+
field: 'facetKeys',
|
|
708
|
+
reason,
|
|
709
|
+
});
|
|
710
|
+
// Nothing references a Reference Type by id, so a policy on one narrows no
|
|
711
|
+
// facet – it states a rule nothing could apply.
|
|
712
|
+
if (searchType.class === undefined) {
|
|
713
|
+
return [issue('facet-keys-not-allowed')];
|
|
714
|
+
}
|
|
715
|
+
return typeof facetKeys.only === 'function'
|
|
716
|
+
? []
|
|
717
|
+
: [issue('facet-keys-only-not-a-function')];
|
|
718
|
+
}
|
|
601
719
|
/**
|
|
602
720
|
* Throw when `searchType` is not a member of `schema` – the port membership
|
|
603
721
|
* guard every engine adapter applies before searching, so a query can never
|
|
@@ -750,6 +868,66 @@ export function datasetField(searchType) {
|
|
|
750
868
|
export function fieldNamed(searchType, name) {
|
|
751
869
|
return searchType.fields.find((field) => field.name === name);
|
|
752
870
|
}
|
|
871
|
+
/**
|
|
872
|
+
* The **document key** of one node of `searchType`: what the projection writes
|
|
873
|
+
* as the document’s `id`, and what a reference into this type stores.
|
|
874
|
+
*
|
|
875
|
+
* The whole rule, in one place, so the projection and any transform that needs
|
|
876
|
+
* to know a node’s key read the same answer:
|
|
877
|
+
*
|
|
878
|
+
* 1. a type declaring no {@link RootType.key} keys on `nodeIri` – the node’s own
|
|
879
|
+
* IRI, as every type does today;
|
|
880
|
+
* 2. otherwise the key field’s `rawValues` become **candidates**: its
|
|
881
|
+
* {@link ReferenceField.transform} (where IRI normalisation lives, so two
|
|
882
|
+
* spellings of one IRI become one candidate), then the
|
|
883
|
+
* {@link isAbsoluteIri} filter, then dedupe and sort – so the default is
|
|
884
|
+
* deterministic whatever order the CONSTRUCT returned them in;
|
|
885
|
+
* 3. no candidate keys on `nodeIri`, without consulting {@link KeyField.pick};
|
|
886
|
+
* 4. otherwise {@link KeyField.pick} chooses (defaulting to the first
|
|
887
|
+
* candidate), and `undefined` keeps `nodeIri`.
|
|
888
|
+
*
|
|
889
|
+
* So a key is always either the node’s own IRI or an IRI the graph offered for
|
|
890
|
+
* that node – never one invented in between. A `pick` returning anything else
|
|
891
|
+
* throws, naming the node and the candidates: it is a bug in a pure function
|
|
892
|
+
* that keys both the document and every reference to it, and letting it through
|
|
893
|
+
* would key those two differently.
|
|
894
|
+
*
|
|
895
|
+
* @param nodeIri the node’s own IRI – the key it falls back to
|
|
896
|
+
* @param rawValues the key field’s values **as a reference field reads them** –
|
|
897
|
+
* a node’s `@id` or a bare-string value, before the field’s `transform`. A
|
|
898
|
+
* caller that reads them itself (a transform working on quads) must apply the
|
|
899
|
+
* same rule and skip a literal-valued object, or it will compute a key for a
|
|
900
|
+
* value the projection never saw and key the two differently.
|
|
901
|
+
*/
|
|
902
|
+
export function documentKeyOf(searchType, nodeIri, rawValues) {
|
|
903
|
+
const key = searchType.key;
|
|
904
|
+
if (key === undefined) {
|
|
905
|
+
return nodeIri;
|
|
906
|
+
}
|
|
907
|
+
// Guaranteed a declared reference field by `searchSchema`, which is what
|
|
908
|
+
// validates a `key` at all.
|
|
909
|
+
const { transform } = fieldNamed(searchType, key.field);
|
|
910
|
+
const candidates = [
|
|
911
|
+
...new Set((transform === undefined ? rawValues : rawValues.map(transform)).filter(isAbsoluteIri)),
|
|
912
|
+
].sort();
|
|
913
|
+
if (candidates.length === 0) {
|
|
914
|
+
return nodeIri;
|
|
915
|
+
}
|
|
916
|
+
const picked = (key.pick ?? firstCandidate)(candidates);
|
|
917
|
+
if (picked === undefined) {
|
|
918
|
+
return nodeIri;
|
|
919
|
+
}
|
|
920
|
+
if (!candidates.includes(picked)) {
|
|
921
|
+
throw new Error(`The “pick” of “${searchType.name}.key” returned “${picked}” for <${nodeIri}>, which is not among its candidates (${candidates
|
|
922
|
+
.map((candidate) => `<${candidate}>`)
|
|
923
|
+
.join(', ')}); a key must be one of them, or “undefined” to keep the node’s own IRI.`);
|
|
924
|
+
}
|
|
925
|
+
return picked;
|
|
926
|
+
}
|
|
927
|
+
/** The {@link KeyField.pick} a type that declares none falls back to. */
|
|
928
|
+
function firstCandidate(candidates) {
|
|
929
|
+
return candidates[0];
|
|
930
|
+
}
|
|
753
931
|
/**
|
|
754
932
|
* The **IR Alias** predicate for a field: `urn:lde:‹SearchType.name›/‹field.name›`.
|
|
755
933
|
* The extraction CONSTRUCT emits a field’s value under this minted predicate, and
|
|
@@ -848,11 +1026,18 @@ export function unixSecondsToIso(seconds) {
|
|
|
848
1026
|
}
|
|
849
1027
|
/**
|
|
850
1028
|
* Derive the **indexed** physical engine field names a declaration produces:
|
|
851
|
-
* the per-locale `search`/`sort` fanout
|
|
852
|
-
* fields are pattern-based and not enumerated
|
|
853
|
-
* and its siblings.
|
|
1029
|
+
* the per-locale `search`/`sort` fanout, and the field a `facet` reads. A
|
|
1030
|
+
* localized `text` field’s display fields are pattern-based and not enumerated
|
|
1031
|
+
* here – see {@link displayFieldName} and its siblings.
|
|
1032
|
+
*
|
|
1033
|
+
* The facet is the one member that depends on more than the declaration: a
|
|
1034
|
+
* facetable reference whose target declares a {@link FacetKeys facet policy}
|
|
1035
|
+
* facets a `${name}_facet` companion rather than itself, and only the `schema`
|
|
1036
|
+
* can resolve that target ({@link inheritedFacetKeys}). Without one the field
|
|
1037
|
+
* facets on its own name – the same reading the projection makes without a
|
|
1038
|
+
* schema, where it cannot re-key a reference either.
|
|
854
1039
|
*/
|
|
855
|
-
export function physicalFields(field) {
|
|
1040
|
+
export function physicalFields(field, schema) {
|
|
856
1041
|
if (field.kind === 'text') {
|
|
857
1042
|
const locales = field.locales;
|
|
858
1043
|
return {
|
|
@@ -862,10 +1047,40 @@ export function physicalFields(field) {
|
|
|
862
1047
|
sort: field.sortable
|
|
863
1048
|
? locales.map((locale) => `${field.name}_sort_${locale}`)
|
|
864
1049
|
: [],
|
|
1050
|
+
// Text is never facetable (`validateSearchType`).
|
|
1051
|
+
facet: undefined,
|
|
865
1052
|
};
|
|
866
1053
|
}
|
|
867
1054
|
return {
|
|
868
1055
|
search: field.searchable !== undefined ? [`${field.name}_search`] : [],
|
|
869
1056
|
sort: [],
|
|
1057
|
+
facet: field.facetable !== true
|
|
1058
|
+
? undefined
|
|
1059
|
+
: inheritedFacetKeys(field, schema) === undefined
|
|
1060
|
+
? field.name
|
|
1061
|
+
: `${field.name}_facet`,
|
|
870
1062
|
};
|
|
871
1063
|
}
|
|
1064
|
+
/**
|
|
1065
|
+
* The {@link FacetKeys facet policy} a field inherits: the `facetKeys` of the
|
|
1066
|
+
* Root Type it names ({@link labelSourceNameOf}), when the field is a facetable
|
|
1067
|
+
* reference and the schema resolves that type. The boundary is *naming the
|
|
1068
|
+
* target* – the same line along which a reference is re-keyed and a join is
|
|
1069
|
+
* drawn – so a reference that names no type inherits nothing, whatever type
|
|
1070
|
+
* its values happen to point at; and a `derive`d reference, which produces its
|
|
1071
|
+
* own values rather than reading a referent, is re-keyed by nothing and so
|
|
1072
|
+
* narrowed by nothing either. The one reading the projection (which writes the
|
|
1073
|
+
* companion) and {@link physicalFields} (which names it) share.
|
|
1074
|
+
*/
|
|
1075
|
+
export function inheritedFacetKeys(field, schema) {
|
|
1076
|
+
if (schema === undefined ||
|
|
1077
|
+
field.kind !== 'reference' ||
|
|
1078
|
+
field.facetable !== true ||
|
|
1079
|
+
field.derive !== undefined) {
|
|
1080
|
+
return undefined;
|
|
1081
|
+
}
|
|
1082
|
+
const targetName = labelSourceNameOf(field);
|
|
1083
|
+
return targetName === undefined
|
|
1084
|
+
? undefined
|
|
1085
|
+
: rootTypeNamed(schema, targetName)?.facetKeys;
|
|
1086
|
+
}
|
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;
|
|
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.
|
|
3
|
+
"version": "0.23.2",
|
|
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",
|