@lde/search 0.13.0 → 0.15.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 +1 -1
- package/dist/adapter.d.ts.map +1 -1
- package/dist/adapter.js +1 -1
- package/dist/engine.d.ts +15 -1
- package/dist/engine.d.ts.map +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/project.d.ts +9 -1
- package/dist/project.d.ts.map +1 -1
- package/dist/project.js +54 -14
- package/dist/schema.d.ts +38 -4
- package/dist/schema.d.ts.map +1 -1
- package/dist/schema.js +89 -0
- package/dist/testing.d.ts.map +1 -1
- package/dist/testing.js +45 -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, referenceFields, referenceTypeNamed, inlineFramingDepth, fieldNamed, ID_FIELD, labelFieldOf, isRangeFacet, isoToUnixSeconds, unixSecondsToIso, } from './schema.js';
|
|
9
|
+
export { assertTypeInSchema, physicalFields, irAlias, displayFieldName, displayFieldPattern, displayLangOf, searchableFields, facetableFields, filterableFields, sortableFields, outputFields, isInternalField, isInlineReference, nestedReferenceType, nestedFieldName, referenceFields, referenceTypeNamed, inlineFramingDepth, fieldNamed, ID_FIELD, labelFieldOf, isRangeFacet, isoToUnixSeconds, unixSecondsToIso, } from './schema.js';
|
|
10
10
|
export type { PhysicalFields } from './schema.js';
|
|
11
11
|
export { physicalNameTokens } from './physical-name.js';
|
|
12
12
|
export { filterOperatorFor, filterOperator, isUnsatisfiable, validateQuery, assertValidQuery, pageForOffset, } from './query.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,eAAe,EACf,kBAAkB,EAClB,kBAAkB,EAClB,UAAU,EACV,QAAQ,EACR,YAAY,EACZ,YAAY,EACZ,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,EACL,iBAAiB,EACjB,cAAc,EACd,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,kBAAkB,EAClB,UAAU,EACV,QAAQ,EACR,YAAY,EACZ,YAAY,EACZ,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,EACL,iBAAiB,EACjB,cAAc,EACd,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,6 +6,6 @@
|
|
|
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, referenceFields, referenceTypeNamed, inlineFramingDepth, fieldNamed, ID_FIELD, labelFieldOf, isRangeFacet, isoToUnixSeconds, unixSecondsToIso, } from './schema.js';
|
|
9
|
+
export { assertTypeInSchema, physicalFields, irAlias, displayFieldName, displayFieldPattern, displayLangOf, searchableFields, facetableFields, filterableFields, sortableFields, outputFields, isInternalField, isInlineReference, nestedReferenceType, nestedFieldName, referenceFields, referenceTypeNamed, inlineFramingDepth, fieldNamed, ID_FIELD, labelFieldOf, isRangeFacet, isoToUnixSeconds, unixSecondsToIso, } from './schema.js';
|
|
10
10
|
export { physicalNameTokens } from './physical-name.js';
|
|
11
11
|
export { filterOperatorFor, filterOperator, isUnsatisfiable, validateQuery, assertValidQuery, pageForOffset, } from './query.js';
|
package/dist/engine.d.ts
CHANGED
|
@@ -116,7 +116,21 @@ export interface SearchHit<OutputField extends string = string> {
|
|
|
116
116
|
*/
|
|
117
117
|
export type ResultDocument<OutputField extends string = string> = Readonly<Partial<Record<OutputField, SearchValue>>>;
|
|
118
118
|
/** A logical field value. */
|
|
119
|
-
export type SearchValue = string | number | boolean | readonly string[] | LocalizedValue | Reference | readonly Reference[];
|
|
119
|
+
export type SearchValue = string | number | boolean | readonly string[] | LocalizedValue | Reference | readonly Reference[] | NestedDocument | readonly NestedDocument[];
|
|
120
|
+
/**
|
|
121
|
+
* A **nested Search Document**: the referent of a surfaced (`output`) inline
|
|
122
|
+
* reference, carrying its Reference Type’s own output fields – each referent’s
|
|
123
|
+
* values grouped together, so a multi-valued reference never degrades into
|
|
124
|
+
* parallel arrays a consumer has to pair by index.
|
|
125
|
+
*
|
|
126
|
+
* `id` is present only when the referent is a named node: nesting carries the
|
|
127
|
+
* referent’s fields, not a document key, so a blank-node referent nests exactly
|
|
128
|
+
* like a named one, minus the `id`. Distinct from {@link Reference}, which is
|
|
129
|
+
* what an `idOnly`/`labelOnly` reference carries – there the IRI *is* the value.
|
|
130
|
+
*/
|
|
131
|
+
export interface NestedDocument {
|
|
132
|
+
readonly [field: string]: SearchValue | undefined;
|
|
133
|
+
}
|
|
120
134
|
/**
|
|
121
135
|
* A JSON-LD-style language map (`@container: @language`, `@set` arrays); the key
|
|
122
136
|
* `und` carries untagged (`@none`) values. The surface flattens it to a
|
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;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AACH,MAAM,WAAW,YAAY,CAC3B,KAAK,SAAS,SAAS,UAAU,EAAE,GAAG,SAAS,UAAU,EAAE;IAE3D;uDACmD;IACnD,QAAQ,CAAC,MAAM,EAAE,YAAY,CAAC,KAAK,CAAC,CAAC;IACrC,MAAM,CAAC,CAAC,SAAS,UAAU,CAAC,KAAK,CAAC,EAChC,UAAU,EAAE,CAAC,EACb,KAAK,EAAE,WAAW,GACjB,OAAO,CAAC,YAAY,CAAC,aAAa,CAAC,CAAC,CAAC,EAAE,cAAc,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;IAC9D;;;;;;;;;;;;;;;;OAgBG;IACH,YAAY,CAAC,CAAC,SAAS,UAAU,CAAC,KAAK,CAAC,EACtC,UAAU,EAAE,CAAC,EACb,OAAO,EAAE,SAAS,WAAW,EAAE,GAC9B,OAAO,CAAC,SAAS,aAAa,CAAC,aAAa,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;CACxD;AAED;;;;;;GAMG;AACH,MAAM,MAAM,aAAa,CAAC,UAAU,SAAS,MAAM,GAAG,MAAM,
|
|
1
|
+
{"version":3,"file":"engine.d.ts","sourceRoot":"","sources":["../src/engine.ts"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
export { projectRoots } from './project.js';
|
|
2
|
-
export type { SearchDocument } from './project.js';
|
|
2
|
+
export type { ProjectedNode, SearchDocument } from './project.js';
|
|
3
3
|
export { defineSearchType, searchSchema, validateSearchType, assertValidSearchType, ID_FIELD, } from './schema.js';
|
|
4
4
|
export type { FieldKind, SearchField, SearchFieldBase, TextField, KeywordField, ReferenceField, NumericField, BooleanField, SearchType, SearchTypeBase, RootType, ReferenceType, RootTypeOf, SearchTypeIssue, SearchSchema, FacetRange, } from './schema.js';
|
|
5
5
|
export type { SearchQuery, Filter, Sort } from './query.js';
|
|
6
|
-
export type { SearchEngine, SearchResult, SearchHit, ResultDocument, SearchValue, LocalizedValue, Reference, FacetBucket, FacetMap, FacetsOutcome, FacetFieldsOf, OutputFieldsOf, } from './engine.js';
|
|
6
|
+
export type { SearchEngine, SearchResult, SearchHit, ResultDocument, SearchValue, LocalizedValue, Reference, NestedDocument, FacetBucket, FacetMap, FacetsOutcome, FacetFieldsOf, OutputFieldsOf, } from './engine.js';
|
|
7
7
|
export type { FramedNode } from './frame-by-type.js';
|
|
8
8
|
//# sourceMappingURL=index.d.ts.map
|
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,EAAE,cAAc,EAAE,MAAM,cAAc,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,EAAE,aAAa,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AAKlE,OAAO,EACL,gBAAgB,EAChB,YAAY,EACZ,kBAAkB,EAClB,qBAAqB,EACrB,QAAQ,GACT,MAAM,aAAa,CAAC;AACrB,YAAY,EACV,SAAS,EACT,WAAW,EACX,eAAe,EACf,SAAS,EACT,YAAY,EACZ,cAAc,EACd,YAAY,EACZ,YAAY,EACZ,UAAU,EACV,cAAc,EACd,QAAQ,EACR,aAAa,EACb,UAAU,EACV,eAAe,EACf,YAAY,EACZ,UAAU,GACX,MAAM,aAAa,CAAC;AAIrB,YAAY,EAAE,WAAW,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,YAAY,CAAC;AAI5D,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
CHANGED
|
@@ -1,10 +1,18 @@
|
|
|
1
1
|
import type { Quad } from '@rdfjs/types';
|
|
2
2
|
import { type FramedNode } from './frame-by-type.js';
|
|
3
3
|
import { type RootType, type SearchSchema, type SearchType } from './schema.js';
|
|
4
|
+
/**
|
|
5
|
+
* A projected node: the fields of one {@link SearchType}, flat. A root always
|
|
6
|
+
* carries the `id` that keys it – it is a {@link SearchDocument} – but an inline
|
|
7
|
+
* referent carries one only when the referent is a named node: a nested document
|
|
8
|
+
* is not a document key, so nesting needs the referent’s fields, not its
|
|
9
|
+
* identity.
|
|
10
|
+
*/
|
|
11
|
+
export type ProjectedNode = Record<string, unknown>;
|
|
4
12
|
/** A flat search document. `id` is the engine document key. */
|
|
5
13
|
export type SearchDocument = {
|
|
6
14
|
id: string;
|
|
7
|
-
} &
|
|
15
|
+
} & ProjectedNode;
|
|
8
16
|
/**
|
|
9
17
|
* Project one framed JSON-LD node into a flat search document: apply each field
|
|
10
18
|
* of the type in declaration order. A field with a `derive` function computes
|
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,EAYL,KAAK,QAAQ,EAEb,KAAK,YAAY,EACjB,KAAK,UAAU,EAEhB,MAAM,aAAa,CAAC;AAErB,+DAA+D;AAC/D,MAAM,MAAM,cAAc,GAAG;IAAE,EAAE,EAAE,MAAM,CAAA;CAAE,GAAG,
|
|
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,EAYL,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;;;;;;;;;;;;;GAaG;AACH,wBAAgB,eAAe,CAC7B,IAAI,EAAE,UAAU,EAChB,UAAU,EAAE,UAAU,EACtB,MAAM,CAAC,EAAE,YAAY,GACpB,cAAc,CAWhB;AAwED;;;;;;;;;;;;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,GACnB,aAAa,CAAC,cAAc,CAAC,CAW/B"}
|
package/dist/project.js
CHANGED
|
@@ -16,6 +16,11 @@ import { assertTypeInSchema, displayFieldName, inlineFramingDepth, irAlias, isIn
|
|
|
16
16
|
* the engine collection definition and the query compiler.
|
|
17
17
|
*/
|
|
18
18
|
export function projectDocument(node, searchType, schema) {
|
|
19
|
+
if (documentKey(node) === undefined) {
|
|
20
|
+
throw new Error(`Cannot project a “${searchType.name}” node without an @id (a blank node label is not one): every search document needs a stable key, and an empty one would collide with other keyless nodes.`);
|
|
21
|
+
}
|
|
22
|
+
// The guard above is what makes this a SearchDocument: projectFields sets `id`
|
|
23
|
+
// for exactly the nodes documentKey resolves.
|
|
19
24
|
const document = projectFields(node, searchType, schema);
|
|
20
25
|
pruneInternalFields(document, searchType, schema);
|
|
21
26
|
return document;
|
|
@@ -36,7 +41,7 @@ function pruneInternalFields(document, searchType, schema) {
|
|
|
36
41
|
delete document[field.name];
|
|
37
42
|
continue;
|
|
38
43
|
}
|
|
39
|
-
// A surfaced inline reference nests its referent(s) as
|
|
44
|
+
// A surfaced inline reference nests its referent(s) as projected node(s);
|
|
40
45
|
// prune those too, by their reference type.
|
|
41
46
|
if (schema === undefined || field.kind !== 'reference') {
|
|
42
47
|
continue;
|
|
@@ -61,16 +66,24 @@ function pruneInternalFields(document, searchType, schema) {
|
|
|
61
66
|
* once the whole nested structure is projected, so a derive at any depth can
|
|
62
67
|
* still read an internal field before it is removed. */
|
|
63
68
|
function projectFields(node, searchType, schema) {
|
|
64
|
-
const id = node
|
|
65
|
-
|
|
66
|
-
throw new Error(`Cannot project a “${searchType.name}” node without an @id: every search document needs a stable key, and an empty one would collide with other keyless nodes.`);
|
|
67
|
-
}
|
|
68
|
-
const document = { id };
|
|
69
|
+
const id = documentKey(node);
|
|
70
|
+
const document = id === undefined ? {} : { id };
|
|
69
71
|
for (const field of searchType.fields) {
|
|
70
72
|
applyField(document, node, field, searchType, schema);
|
|
71
73
|
}
|
|
72
74
|
return document;
|
|
73
75
|
}
|
|
76
|
+
/**
|
|
77
|
+
* The document key a framed node carries, if any. A blank node label (`_:b0`) is
|
|
78
|
+
* not one: framing mints it per call, so it recurs across documents and can
|
|
79
|
+
* change when unrelated triples do. A node bearing one is therefore projected as
|
|
80
|
+
* if framing had pruned its `@id` – which it does whenever the label occurs only
|
|
81
|
+
* once in the framing results.
|
|
82
|
+
*/
|
|
83
|
+
function documentKey(node) {
|
|
84
|
+
const id = node['@id'];
|
|
85
|
+
return typeof id === 'string' && !id.startsWith('_:') ? id : undefined;
|
|
86
|
+
}
|
|
74
87
|
/**
|
|
75
88
|
* Project a single type over a known set of `roots` – the per-type, roots-given
|
|
76
89
|
* projection. The roots are supplied by the caller (the pipeline selector)
|
|
@@ -197,16 +210,35 @@ function foldedSearchValue(values) {
|
|
|
197
210
|
return fold(values.join(' ')).trim();
|
|
198
211
|
}
|
|
199
212
|
/**
|
|
200
|
-
* Project a faceted
|
|
201
|
-
*
|
|
202
|
-
*
|
|
213
|
+
* Project a faceted field: dedupe (after the optional transform), write the
|
|
214
|
+
* value field, and – when `searchable` – its folded `${name}_search` companion.
|
|
215
|
+
* `keyword` reads literals; `reference` reads IRIs (the caller passes the
|
|
203
216
|
* already-read raw values).
|
|
217
|
+
*
|
|
218
|
+
* **`array` decides the shape**, as it does for every other kind: a declared
|
|
219
|
+
* `array` field stores a list, and a single-valued one stores the first value –
|
|
220
|
+
* the graph may still carry several, exactly as it may carry several literals
|
|
221
|
+
* for a single-valued `integer` or `date` (which take the first too). Honouring
|
|
222
|
+
* it here is what keeps the projection, the engine collection definition
|
|
223
|
+
* (`string` vs `string[]`) and the API output type saying the same thing about
|
|
224
|
+
* one declaration; a list written into a single-valued field is a document the
|
|
225
|
+
* engine rejects, or – where it does not type-check, as inside a nested
|
|
226
|
+
* document – a value the API cannot serialize.
|
|
204
227
|
*/
|
|
205
228
|
function applyFacet(document, raw, field) {
|
|
206
229
|
const values = dedupe(field.transform ? raw.map(field.transform) : raw);
|
|
207
|
-
|
|
230
|
+
const folded = dedupe(values.map((value) => fold(value)));
|
|
231
|
+
const searchField = physicalFields(field).search[0];
|
|
232
|
+
if (field.array === true) {
|
|
233
|
+
setArray(document, field.name, values);
|
|
234
|
+
if (field.searchable) {
|
|
235
|
+
setArray(document, searchField, folded);
|
|
236
|
+
}
|
|
237
|
+
return;
|
|
238
|
+
}
|
|
239
|
+
setString(document, field.name, values[0]);
|
|
208
240
|
if (field.searchable) {
|
|
209
|
-
|
|
241
|
+
setString(document, searchField, folded[0]);
|
|
210
242
|
}
|
|
211
243
|
}
|
|
212
244
|
/**
|
|
@@ -214,7 +246,10 @@ function applyFacet(document, raw, field) {
|
|
|
214
246
|
* {@link irAlias IR Alias} are each projected through the reference’s
|
|
215
247
|
* {@link ReferenceType} (whose own fields read their own aliases, minted against
|
|
216
248
|
* the reference type) and attached under the field’s name – a nested
|
|
217
|
-
* {@link
|
|
249
|
+
* {@link ProjectedNode} for a single reference, an array for an `array` one.
|
|
250
|
+
* A referent needs **no identity**: nesting carries its fields, not a document
|
|
251
|
+
* key, so a blank-node referent – whose `@id` JSON-LD 1.1 framing prunes when its
|
|
252
|
+
* label occurs once – nests exactly like a named one, minus the `id`.
|
|
218
253
|
* The referent is projected in full – internal fields included – so the
|
|
219
254
|
* declaring type’s (or the reference type’s own) derives can read them;
|
|
220
255
|
* {@link pruneInternalFields} then removes the internal fields from a *surfaced*
|
|
@@ -233,8 +268,13 @@ function applyInlineReference(document, node, alias, field, schema) {
|
|
|
233
268
|
}
|
|
234
269
|
const referents = valuesOf(node, alias)
|
|
235
270
|
.filter(isObject)
|
|
236
|
-
.
|
|
237
|
-
|
|
271
|
+
.map((referent) => projectFields(referent, referenceType, schema))
|
|
272
|
+
// Fields, not identity, are what makes something a referent: a literal
|
|
273
|
+
// value object under the alias (dirty source data), or a node this
|
|
274
|
+
// reference type reads nothing from, projects nothing and is no referent.
|
|
275
|
+
// Nesting it would hand the writer a content-free document – and, for a
|
|
276
|
+
// single-valued reference, let it win the slot over a real referent.
|
|
277
|
+
.filter((referent) => Object.keys(referent).length > 0);
|
|
238
278
|
if (referents.length === 0) {
|
|
239
279
|
return;
|
|
240
280
|
}
|
package/dist/schema.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { ProjectedNode } from './project.js';
|
|
2
2
|
/**
|
|
3
3
|
* The engine-neutral kind of a queryable field. It drives every downstream
|
|
4
4
|
* behavior: which physical fields the projection emits, the engine
|
|
@@ -58,7 +58,10 @@ export interface SearchFieldBase {
|
|
|
58
58
|
/** Framed-IR predicate IRI to project from. Omit for a field populated by
|
|
59
59
|
* {@link SearchFieldBase.derive} (or outside the projection entirely). */
|
|
60
60
|
readonly path?: string;
|
|
61
|
-
/** Multi-valued.
|
|
61
|
+
/** Multi-valued: the field stores a list. Single-valued (the default) stores
|
|
62
|
+
* the FIRST value the graph carries, whatever their number – for every kind
|
|
63
|
+
* alike – so one declaration cannot mean a list to the projection and a
|
|
64
|
+
* scalar to the collection definition and the API. */
|
|
62
65
|
readonly array?: boolean;
|
|
63
66
|
/** Always present: a non-null scalar in the API output and
|
|
64
67
|
* a non-optional field in the engine index. Moot for arrays/booleans/`id`,
|
|
@@ -82,9 +85,12 @@ export interface SearchFieldBase {
|
|
|
82
85
|
* {@link isInternalField internal} field). Return `undefined` to leave the
|
|
83
86
|
* field absent. The field still carries full query/schema/output behaviour
|
|
84
87
|
* like any other. Reading only the document is what keeps `path` the complete
|
|
85
|
-
* statement of what the projection reads from the graph.
|
|
88
|
+
* statement of what the projection reads from the graph. The node it receives
|
|
89
|
+
* is the search document for a root type, and the referent’s own
|
|
90
|
+
* projected fields for a Reference Type – which carry an `id` only when the
|
|
91
|
+
* referent is a named node, never for a blank-node one.
|
|
86
92
|
*/
|
|
87
|
-
readonly derive?: (document:
|
|
93
|
+
readonly derive?: (document: ProjectedNode) => unknown;
|
|
88
94
|
}
|
|
89
95
|
/** Full-text inclusion with a `query_by` weight (folded; per-locale for
|
|
90
96
|
* localized text). Presence is what makes a field searchable. */
|
|
@@ -315,6 +321,34 @@ export declare function isInlineReference(field: SearchField): field is Referenc
|
|
|
315
321
|
readonly strategy: 'inline';
|
|
316
322
|
};
|
|
317
323
|
};
|
|
324
|
+
/**
|
|
325
|
+
* The {@link ReferenceType} a **surfaced** inline reference nests – an
|
|
326
|
+
* `output` inline reference’s referent shape – or `undefined` for every other
|
|
327
|
+
* field. The one predicate the collection definition, result reconstruction and
|
|
328
|
+
* the API surfaces share, so they cannot disagree about which fields carry a
|
|
329
|
+
* nested Search Document and which carry a bare IRI: an inline reference
|
|
330
|
+
* declaring no Role is a reading device, pruned before the writer
|
|
331
|
+
* ({@link isInternalField}), and a `labelOnly`/`idOnly` reference stays an id
|
|
332
|
+
* (plus a resolved label).
|
|
333
|
+
*
|
|
334
|
+
* A schema built by {@link searchSchema} always resolves the reference type of
|
|
335
|
+
* its own types; `undefined` for a type declared against another schema.
|
|
336
|
+
*/
|
|
337
|
+
export declare function nestedReferenceType(schema: SearchSchema, field: SearchField): ReferenceType | undefined;
|
|
338
|
+
/**
|
|
339
|
+
* The Physical Field name a nested field carries in the engine: the surfaced
|
|
340
|
+
* inline reference’s own name, then the referent’s physical name –
|
|
341
|
+
* `media.contentUrl`, `media.label_nl`, and `media.thumbnail.contentUrl` one
|
|
342
|
+
* hop deeper. The nested counterpart of {@link physicalFields}, and equally the
|
|
343
|
+
* single home of its convention: the collection definition declares these
|
|
344
|
+
* names, so a second consumer (a nested filter compiler) reads them from here
|
|
345
|
+
* rather than restating the separator.
|
|
346
|
+
*
|
|
347
|
+
* The nested Search Document itself is keyed by the referent’s own field names
|
|
348
|
+
* ({@link ProjectedNode}) – the qualification is how an engine that stores
|
|
349
|
+
* nested documents flat addresses them, never what the projection writes.
|
|
350
|
+
*/
|
|
351
|
+
export declare function nestedFieldName(parent: string, name: string): string;
|
|
318
352
|
/**
|
|
319
353
|
* The framing depth a Root Type needs: how many hops the inline reference graph
|
|
320
354
|
* reaches from it (`Dataset → Subset → Measurement` is two), floored at one so
|
package/dist/schema.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"schema.d.ts","sourceRoot":"","sources":["../src/schema.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,
|
|
1
|
+
{"version":3,"file":"schema.d.ts","sourceRoot":"","sources":["../src/schema.ts"],"names":[],"mappings":"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"}
|
package/dist/schema.js
CHANGED
|
@@ -77,6 +77,7 @@ export function searchSchema(...types) {
|
|
|
77
77
|
.filter((searchType) => !isRootType(searchType))
|
|
78
78
|
.map((searchType) => [searchType.name, searchType]));
|
|
79
79
|
assertResolvableInlineReferences(types, referenceTypes);
|
|
80
|
+
assertServiceableNestedFields(referenceTypes);
|
|
80
81
|
assertResolvableLabelSources(types);
|
|
81
82
|
// The one blessed cast: only this validated constructor mints the brand.
|
|
82
83
|
const schema = new Map(types
|
|
@@ -99,6 +100,40 @@ export function referenceTypeNamed(schema, name) {
|
|
|
99
100
|
export function isInlineReference(field) {
|
|
100
101
|
return field.kind === 'reference' && field.ref?.strategy === 'inline';
|
|
101
102
|
}
|
|
103
|
+
/**
|
|
104
|
+
* The {@link ReferenceType} a **surfaced** inline reference nests – an
|
|
105
|
+
* `output` inline reference’s referent shape – or `undefined` for every other
|
|
106
|
+
* field. The one predicate the collection definition, result reconstruction and
|
|
107
|
+
* the API surfaces share, so they cannot disagree about which fields carry a
|
|
108
|
+
* nested Search Document and which carry a bare IRI: an inline reference
|
|
109
|
+
* declaring no Role is a reading device, pruned before the writer
|
|
110
|
+
* ({@link isInternalField}), and a `labelOnly`/`idOnly` reference stays an id
|
|
111
|
+
* (plus a resolved label).
|
|
112
|
+
*
|
|
113
|
+
* A schema built by {@link searchSchema} always resolves the reference type of
|
|
114
|
+
* its own types; `undefined` for a type declared against another schema.
|
|
115
|
+
*/
|
|
116
|
+
export function nestedReferenceType(schema, field) {
|
|
117
|
+
return isInlineReference(field) && field.output === true
|
|
118
|
+
? referenceTypeNamed(schema, field.ref.typeName)
|
|
119
|
+
: undefined;
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* The Physical Field name a nested field carries in the engine: the surfaced
|
|
123
|
+
* inline reference’s own name, then the referent’s physical name –
|
|
124
|
+
* `media.contentUrl`, `media.label_nl`, and `media.thumbnail.contentUrl` one
|
|
125
|
+
* hop deeper. The nested counterpart of {@link physicalFields}, and equally the
|
|
126
|
+
* single home of its convention: the collection definition declares these
|
|
127
|
+
* names, so a second consumer (a nested filter compiler) reads them from here
|
|
128
|
+
* rather than restating the separator.
|
|
129
|
+
*
|
|
130
|
+
* The nested Search Document itself is keyed by the referent’s own field names
|
|
131
|
+
* ({@link ProjectedNode}) – the qualification is how an engine that stores
|
|
132
|
+
* nested documents flat addresses them, never what the projection writes.
|
|
133
|
+
*/
|
|
134
|
+
export function nestedFieldName(parent, name) {
|
|
135
|
+
return `${parent}.${name}`;
|
|
136
|
+
}
|
|
102
137
|
/**
|
|
103
138
|
* The framing depth a Root Type needs: how many hops the inline reference graph
|
|
104
139
|
* reaches from it (`Dataset → Subset → Measurement` is two), floored at one so
|
|
@@ -145,6 +180,17 @@ function assertResolvableInlineReferences(types, referenceTypes) {
|
|
|
145
180
|
if (!referenceTypes.has(field.ref.typeName)) {
|
|
146
181
|
throw new Error(`Inline reference “${searchType.name}.${field.name}” names “${field.ref.typeName}”, which is not a declared reference type; declare a reference type (a SearchType with no class) with that name.`);
|
|
147
182
|
}
|
|
183
|
+
// The two jobs of an inline reference, told apart only by Roles: a
|
|
184
|
+
// reading device (no Role, pruned before the writer) or a surfaced nested
|
|
185
|
+
// object (`output`). Anything else asks an engine to search, filter,
|
|
186
|
+
// facet or sort on a value that is stored as a nested document – which no
|
|
187
|
+
// query compiler serves, so it would be ignored per query.
|
|
188
|
+
const extraRoles = unserviceableNestedRoles(field);
|
|
189
|
+
if (extraRoles.length > 0) {
|
|
190
|
+
throw new Error(`Inline reference “${searchType.name}.${field.name}” declares ${extraRoles
|
|
191
|
+
.map((role) => `“${role}”`)
|
|
192
|
+
.join(', ')}, which it cannot serve: an inline reference is either a reading device (no Role) or surfaced (“output”).`);
|
|
193
|
+
}
|
|
148
194
|
}
|
|
149
195
|
}
|
|
150
196
|
for (const referenceType of referenceTypes.values()) {
|
|
@@ -166,6 +212,49 @@ function assertNoInlineCycle(referenceType, referenceTypes, onPath) {
|
|
|
166
212
|
assertNoInlineCycle(referent, referenceTypes, extended);
|
|
167
213
|
}
|
|
168
214
|
}
|
|
215
|
+
/**
|
|
216
|
+
* Every field of a {@link ReferenceType} must be one the nesting can actually
|
|
217
|
+
* serve. A nested field carries **`output` only**: it is stored as part of its
|
|
218
|
+
* referent and read back with it, so it never becomes an indexed, addressable
|
|
219
|
+
* field of its own. `searchable`, `filterable`, `facetable` and `sortable` on a
|
|
220
|
+
* nested field would need query-compiler and engine support that no engine port
|
|
221
|
+
* declares, and a `labelSource` would need a label lookup no reconstruction
|
|
222
|
+
* runs – each would be silently ignored per query. A Role-less field stays an
|
|
223
|
+
* {@link isInternalField Internal Field}, the reading device that is the other
|
|
224
|
+
* half of an inline reference’s job.
|
|
225
|
+
*
|
|
226
|
+
* Checked schema-wide, like the label sources and for the same reason: a single
|
|
227
|
+
* declaration cannot see whether it is a Reference Type at all.
|
|
228
|
+
*/
|
|
229
|
+
function assertServiceableNestedFields(referenceTypes) {
|
|
230
|
+
for (const referenceType of referenceTypes.values()) {
|
|
231
|
+
for (const field of referenceType.fields) {
|
|
232
|
+
const unserviceable = unserviceableNestedRoles(field);
|
|
233
|
+
if (unserviceable.length > 0) {
|
|
234
|
+
throw new Error(`Nested field “${referenceType.name}.${field.name}” declares ${unserviceable
|
|
235
|
+
.map((role) => `“${role}”`)
|
|
236
|
+
.join(', ')}, which an inline reference cannot serve; a nested field carries “output” only.`);
|
|
237
|
+
}
|
|
238
|
+
if (field.labelSource !== undefined) {
|
|
239
|
+
throw new Error(`Nested field “${referenceType.name}.${field.name}” declares a label source, which an inline reference cannot serve; nest the referent through an inline reference instead of resolving a label for it.`);
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
/** The Roles a field declares that nesting cannot serve – every Role but
|
|
245
|
+
* `output`, in declaration order, so a message names them all at once. */
|
|
246
|
+
function unserviceableNestedRoles(field) {
|
|
247
|
+
return UNSERVICEABLE_NESTED_ROLES.filter((role) => role === 'searchable'
|
|
248
|
+
? field.searchable !== undefined
|
|
249
|
+
: field[role] === true);
|
|
250
|
+
}
|
|
251
|
+
/** The Roles a nested field cannot carry – everything but `output`. */
|
|
252
|
+
const UNSERVICEABLE_NESTED_ROLES = [
|
|
253
|
+
'searchable',
|
|
254
|
+
'filterable',
|
|
255
|
+
'facetable',
|
|
256
|
+
'sortable',
|
|
257
|
+
];
|
|
169
258
|
/**
|
|
170
259
|
* The text field a label source serves labels from – the ‘label’ convention
|
|
171
260
|
* in one place: an `output` (something to reconstruct a label from),
|
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;AAkBhD;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,4BAA4B,CAC1C,IAAI,EAAE,MAAM,EACZ,MAAM,EAAE,MAAM,YAAY,GACzB,IAAI,CA0NN"}
|
package/dist/testing.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { describe, expect, it } from 'vitest';
|
|
2
2
|
import { filterOperatorFor, } from './query.js';
|
|
3
|
-
import { facetableFields, filterableFields, ID_FIELD, } from './schema.js';
|
|
3
|
+
import { facetableFields, filterableFields, ID_FIELD, 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
|
|
@@ -145,6 +145,50 @@ export function describeSearchEngineContract(name, engine) {
|
|
|
145
145
|
await expect(engine().searchFacets(searchType, [])).resolves.toEqual([]);
|
|
146
146
|
}
|
|
147
147
|
});
|
|
148
|
+
it('serves a surfaced inline reference as nested documents, never a bare IRI', async () => {
|
|
149
|
+
// The API device of an Inline Reference (ADR 11): a reference declared
|
|
150
|
+
// `inline` with `output` reaches the caller as its referent’s own fields,
|
|
151
|
+
// grouped per referent. Every engine owes that shape – reconstruction
|
|
152
|
+
// lives below the surfaces, so a surface never has to rebuild it – and an
|
|
153
|
+
// adapter that stores the reference as an IRI fails here rather than in
|
|
154
|
+
// one deployment’s UI.
|
|
155
|
+
for (const searchType of types()) {
|
|
156
|
+
const nested = [];
|
|
157
|
+
for (const field of searchType.fields) {
|
|
158
|
+
const referenceType = nestedReferenceType(engine().schema, field);
|
|
159
|
+
if (referenceType !== undefined) {
|
|
160
|
+
nested.push({ field, referenceType });
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
if (nested.length === 0) {
|
|
164
|
+
continue;
|
|
165
|
+
}
|
|
166
|
+
const result = await engine().search(searchType, {
|
|
167
|
+
...browse(searchType),
|
|
168
|
+
limit: 20,
|
|
169
|
+
});
|
|
170
|
+
for (const hit of result.hits) {
|
|
171
|
+
for (const { field, referenceType } of nested) {
|
|
172
|
+
const value = hit.document[field.name];
|
|
173
|
+
if (value === undefined) {
|
|
174
|
+
continue; // A referent-less document nests nothing.
|
|
175
|
+
}
|
|
176
|
+
// A multi-valued reference keeps one document per referent, so a
|
|
177
|
+
// consumer never pairs parallel arrays by index.
|
|
178
|
+
expect(Array.isArray(value)).toBe(field.array === true);
|
|
179
|
+
const referents = (Array.isArray(value) ? value : [value]);
|
|
180
|
+
const declared = new Set([
|
|
181
|
+
ID_FIELD,
|
|
182
|
+
...outputFields(referenceType).map((nestedField) => nestedField.name),
|
|
183
|
+
]);
|
|
184
|
+
for (const referent of referents) {
|
|
185
|
+
expect(referent).toBeTypeOf('object');
|
|
186
|
+
expect(Object.keys(referent).every((key) => declared.has(key))).toBe(true);
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
});
|
|
148
192
|
it('answers a browse query with hits, a total and facets', async () => {
|
|
149
193
|
for (const searchType of types()) {
|
|
150
194
|
const result = await engine().search(searchType, browse(searchType));
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lde/search",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.15.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",
|