@lde/search 0.14.0 → 0.16.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 +3 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/project.d.ts +18 -3
- package/dist/project.d.ts.map +1 -1
- package/dist/project.js +62 -16
- package/dist/schema.d.ts +90 -8
- package/dist/schema.d.ts.map +1 -1
- package/dist/schema.js +150 -1
- package/dist/testing.d.ts.map +1 -1
- package/dist/testing.js +45 -1
- package/package.json +2 -2
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, datasetField, 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,YAAY,EACZ,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, datasetField, 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 { ProjectedNode, SearchDocument } from './project.js';
|
|
2
|
+
export type { ProjectedNode, ProjectionContext, SearchDocument, } from './project.js';
|
|
3
3
|
export { defineSearchType, searchSchema, validateSearchType, assertValidSearchType, ID_FIELD, } from './schema.js';
|
|
4
|
-
export type { FieldKind, SearchField, SearchFieldBase, TextField, KeywordField, ReferenceField, NumericField, BooleanField, SearchType, SearchTypeBase, RootType, ReferenceType, RootTypeOf, SearchTypeIssue, SearchSchema, FacetRange, } from './schema.js';
|
|
4
|
+
export type { FieldKind, SearchField, SearchFieldBase, TextField, KeywordField, ReferenceField, NumericField, BooleanField, SearchType, SearchTypeBase, RootType, ReferenceType, RootTypeOf, SearchTypeIssue, SearchSchema, FacetRange, ProjectionValue, } 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,
|
|
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,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,EACV,eAAe,GAChB,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,6 +1,6 @@
|
|
|
1
1
|
import type { Quad } from '@rdfjs/types';
|
|
2
2
|
import { type FramedNode } from './frame-by-type.js';
|
|
3
|
-
import { type RootType, type SearchSchema, type SearchType } from './schema.js';
|
|
3
|
+
import { type ProjectionValue, type RootType, type SearchSchema, type SearchType } from './schema.js';
|
|
4
4
|
/**
|
|
5
5
|
* A projected node: the fields of one {@link SearchType}, flat. A root always
|
|
6
6
|
* carries the `id` that keys it – it is a {@link SearchDocument} – but an inline
|
|
@@ -13,6 +13,21 @@ export type ProjectedNode = Record<string, unknown>;
|
|
|
13
13
|
export type SearchDocument = {
|
|
14
14
|
id: string;
|
|
15
15
|
} & ProjectedNode;
|
|
16
|
+
/**
|
|
17
|
+
* What the projection knows that the graph does not say: the values a run
|
|
18
|
+
* carries about the *indexing* rather than about the data. They reach a
|
|
19
|
+
* declaration two ways – a field declaring {@link KeywordField.from `from`} is
|
|
20
|
+
* populated from one, and every `derive` receives the whole context as its
|
|
21
|
+
* second argument, so a derive can relate a projected value to it.
|
|
22
|
+
*
|
|
23
|
+
* Empty when the caller supplies nothing: projecting a graph in isolation (a
|
|
24
|
+
* test, a one-off) is legitimate, and a field over an absent value is simply
|
|
25
|
+
* left unpopulated, exactly as a `path` that matched nothing is.
|
|
26
|
+
*/
|
|
27
|
+
export interface ProjectionContext extends Partial<Readonly<Record<ProjectionValue, string>>> {
|
|
28
|
+
/** The IRI of the dataset being indexed. */
|
|
29
|
+
readonly dataset?: string;
|
|
30
|
+
}
|
|
16
31
|
/**
|
|
17
32
|
* Project one framed JSON-LD node into a flat search document: apply each field
|
|
18
33
|
* of the type in declaration order. A field with a `derive` function computes
|
|
@@ -27,7 +42,7 @@ export type SearchDocument = {
|
|
|
27
42
|
* fans out to come from {@link physicalFields}, the single source shared with
|
|
28
43
|
* the engine collection definition and the query compiler.
|
|
29
44
|
*/
|
|
30
|
-
export declare function projectDocument(node: FramedNode, searchType: SearchType, schema?: SearchSchema): SearchDocument;
|
|
45
|
+
export declare function projectDocument(node: FramedNode, searchType: SearchType, schema?: SearchSchema, context?: ProjectionContext): SearchDocument;
|
|
31
46
|
/**
|
|
32
47
|
* Project a single type over a known set of `roots` – the per-type, roots-given
|
|
33
48
|
* projection. The roots are supplied by the caller (the pipeline selector)
|
|
@@ -41,5 +56,5 @@ export declare function projectDocument(node: FramedNode, searchType: SearchType
|
|
|
41
56
|
* Consumes `quads` once, so it accepts any `Iterable` – a batch’s materialized
|
|
42
57
|
* array or a chained generator merging several readers.
|
|
43
58
|
*/
|
|
44
|
-
export declare function projectRoots(quads: Iterable<Quad>, roots: readonly string[], schema: SearchSchema, searchType: RootType): AsyncIterable<SearchDocument>;
|
|
59
|
+
export declare function projectRoots(quads: Iterable<Quad>, roots: readonly string[], schema: SearchSchema, searchType: RootType, context?: ProjectionContext): AsyncIterable<SearchDocument>;
|
|
45
60
|
//# sourceMappingURL=project.d.ts.map
|
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,EAWL,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;AAyED;;;;;;;;;;;;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,CAW/B"}
|
package/dist/project.js
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { fold } from '@lde/text-normalization';
|
|
2
2
|
import { buildSubjectIndex, frameSubjects, } from './frame-by-type.js';
|
|
3
3
|
import { assertTypeInSchema, displayFieldName, inlineFramingDepth, irAlias, isInternalField, isInlineReference, isoToUnixSeconds, physicalFields, referenceTypeNamed, } from './schema.js';
|
|
4
|
+
/** The context a projection runs with when its caller supplied none. */
|
|
5
|
+
const NO_PROJECTION_CONTEXT = {};
|
|
4
6
|
/**
|
|
5
7
|
* Project one framed JSON-LD node into a flat search document: apply each field
|
|
6
8
|
* of the type in declaration order. A field with a `derive` function computes
|
|
@@ -15,13 +17,13 @@ import { assertTypeInSchema, displayFieldName, inlineFramingDepth, irAlias, isIn
|
|
|
15
17
|
* fans out to come from {@link physicalFields}, the single source shared with
|
|
16
18
|
* the engine collection definition and the query compiler.
|
|
17
19
|
*/
|
|
18
|
-
export function projectDocument(node, searchType, schema) {
|
|
20
|
+
export function projectDocument(node, searchType, schema, context = NO_PROJECTION_CONTEXT) {
|
|
19
21
|
if (documentKey(node) === undefined) {
|
|
20
22
|
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
23
|
}
|
|
22
24
|
// The guard above is what makes this a SearchDocument: projectFields sets `id`
|
|
23
25
|
// for exactly the nodes documentKey resolves.
|
|
24
|
-
const document = projectFields(node, searchType, schema);
|
|
26
|
+
const document = projectFields(node, searchType, schema, context);
|
|
25
27
|
pruneInternalFields(document, searchType, schema);
|
|
26
28
|
return document;
|
|
27
29
|
}
|
|
@@ -65,11 +67,11 @@ function pruneInternalFields(document, searchType, schema) {
|
|
|
65
67
|
* Pruning is deferred to a single recursive pass ({@link pruneInternalFields})
|
|
66
68
|
* once the whole nested structure is projected, so a derive at any depth can
|
|
67
69
|
* still read an internal field before it is removed. */
|
|
68
|
-
function projectFields(node, searchType, schema) {
|
|
70
|
+
function projectFields(node, searchType, schema, context) {
|
|
69
71
|
const id = documentKey(node);
|
|
70
72
|
const document = id === undefined ? {} : { id };
|
|
71
73
|
for (const field of searchType.fields) {
|
|
72
|
-
applyField(document, node, field, searchType, schema);
|
|
74
|
+
applyField(document, node, field, searchType, schema, context);
|
|
73
75
|
}
|
|
74
76
|
return document;
|
|
75
77
|
}
|
|
@@ -97,7 +99,7 @@ function documentKey(node) {
|
|
|
97
99
|
* Consumes `quads` once, so it accepts any `Iterable` – a batch’s materialized
|
|
98
100
|
* array or a chained generator merging several readers.
|
|
99
101
|
*/
|
|
100
|
-
export async function* projectRoots(quads, roots, schema, searchType) {
|
|
102
|
+
export async function* projectRoots(quads, roots, schema, searchType, context = NO_PROJECTION_CONTEXT) {
|
|
101
103
|
assertTypeInSchema(schema, searchType);
|
|
102
104
|
const index = buildSubjectIndex(quads);
|
|
103
105
|
// Distinct roots only. A selector may return an IRI more than once – a
|
|
@@ -106,12 +108,18 @@ export async function* projectRoots(quads, roots, schema, searchType) {
|
|
|
106
108
|
// duplicate document under the same `id`.
|
|
107
109
|
const depth = inlineFramingDepth(schema, searchType);
|
|
108
110
|
for await (const node of frameSubjects(index, [...new Set(roots)], depth)) {
|
|
109
|
-
yield projectDocument(node, searchType, schema);
|
|
111
|
+
yield projectDocument(node, searchType, schema, context);
|
|
110
112
|
}
|
|
111
113
|
}
|
|
112
|
-
function applyField(document, node, field, searchType, schema) {
|
|
114
|
+
function applyField(document, node, field, searchType, schema, context) {
|
|
115
|
+
// The three value sources, mutually exclusive by declaration
|
|
116
|
+
// (`validateSearchType`): a projection value, a computed value, a graph path.
|
|
117
|
+
if ((field.kind === 'keyword' || field.kind === 'reference') &&
|
|
118
|
+
field.from !== undefined) {
|
|
119
|
+
return applyProjectionValue(document, field, field.from, context);
|
|
120
|
+
}
|
|
113
121
|
if (field.derive !== undefined) {
|
|
114
|
-
const value = field.derive(document);
|
|
122
|
+
const value = field.derive(document, context);
|
|
115
123
|
if (value !== undefined) {
|
|
116
124
|
document[field.name] = value;
|
|
117
125
|
}
|
|
@@ -134,7 +142,7 @@ function applyField(document, node, field, searchType, schema) {
|
|
|
134
142
|
// project nothing rather than fall through and emit the referent IRIs under
|
|
135
143
|
// the field name (the wrong shape).
|
|
136
144
|
if (schema !== undefined) {
|
|
137
|
-
applyInlineReference(document, node, alias, field, schema);
|
|
145
|
+
applyInlineReference(document, node, alias, field, schema, context);
|
|
138
146
|
}
|
|
139
147
|
return;
|
|
140
148
|
}
|
|
@@ -210,18 +218,56 @@ function foldedSearchValue(values) {
|
|
|
210
218
|
return fold(values.join(' ')).trim();
|
|
211
219
|
}
|
|
212
220
|
/**
|
|
213
|
-
* Project a faceted
|
|
214
|
-
*
|
|
215
|
-
*
|
|
221
|
+
* Project a faceted field: dedupe (after the optional transform), write the
|
|
222
|
+
* value field, and – when `searchable` – its folded `${name}_search` companion.
|
|
223
|
+
* `keyword` reads literals; `reference` reads IRIs (the caller passes the
|
|
216
224
|
* already-read raw values).
|
|
225
|
+
*
|
|
226
|
+
* **`array` decides the shape**, as it does for every other kind: a declared
|
|
227
|
+
* `array` field stores a list, and a single-valued one stores the first value –
|
|
228
|
+
* the graph may still carry several, exactly as it may carry several literals
|
|
229
|
+
* for a single-valued `integer` or `date` (which take the first too). Honouring
|
|
230
|
+
* it here is what keeps the projection, the engine collection definition
|
|
231
|
+
* (`string` vs `string[]`) and the API output type saying the same thing about
|
|
232
|
+
* one declaration; a list written into a single-valued field is a document the
|
|
233
|
+
* engine rejects, or – where it does not type-check, as inside a nested
|
|
234
|
+
* document – a value the API cannot serialize.
|
|
217
235
|
*/
|
|
218
236
|
function applyFacet(document, raw, field) {
|
|
219
237
|
const values = dedupe(field.transform ? raw.map(field.transform) : raw);
|
|
220
|
-
|
|
238
|
+
const folded = dedupe(values.map((value) => fold(value)));
|
|
239
|
+
const searchField = physicalFields(field).search[0];
|
|
240
|
+
if (field.array === true) {
|
|
241
|
+
setArray(document, field.name, values);
|
|
242
|
+
if (field.searchable) {
|
|
243
|
+
setArray(document, searchField, folded);
|
|
244
|
+
}
|
|
245
|
+
return;
|
|
246
|
+
}
|
|
247
|
+
setString(document, field.name, values[0]);
|
|
221
248
|
if (field.searchable) {
|
|
222
|
-
|
|
249
|
+
setString(document, searchField, folded[0]);
|
|
223
250
|
}
|
|
224
251
|
}
|
|
252
|
+
/**
|
|
253
|
+
* Project a field declared over a {@link ProjectionContext} value: read the
|
|
254
|
+
* value the run carries and write it exactly as a graph-read one, through
|
|
255
|
+
* {@link applyFacet} – so `array`, `transform` and the folded `searchable`
|
|
256
|
+
* companion mean the same thing for a declaration over the dataset as for one
|
|
257
|
+
* over a path, and the collection definition needs no special case.
|
|
258
|
+
*
|
|
259
|
+
* A value the context does not carry writes nothing: a projection run outside a
|
|
260
|
+
* pipeline (a test, a one-off) leaves the field absent rather than inventing a
|
|
261
|
+
* placeholder, the same as a `path` that matched nothing.
|
|
262
|
+
*/
|
|
263
|
+
function applyProjectionValue(document, field, from, context) {
|
|
264
|
+
// `ProjectionContext` is keyed by `ProjectionValue`, so the declaration reads
|
|
265
|
+
// the context directly: a projection value that has no context key, or a
|
|
266
|
+
// context key no declaration can name, is a compile error rather than a
|
|
267
|
+
// silently unpopulated field.
|
|
268
|
+
const value = context[from];
|
|
269
|
+
applyFacet(document, value === undefined ? [] : [value], field);
|
|
270
|
+
}
|
|
225
271
|
/**
|
|
226
272
|
* Project an inline reference: the referent node(s) embedded under the field’s
|
|
227
273
|
* {@link irAlias IR Alias} are each projected through the reference’s
|
|
@@ -239,7 +285,7 @@ function applyFacet(document, raw, field) {
|
|
|
239
285
|
* the schema’s declared depth. The referent type is guaranteed declared by
|
|
240
286
|
* {@link searchSchema}.
|
|
241
287
|
*/
|
|
242
|
-
function applyInlineReference(document, node, alias, field, schema) {
|
|
288
|
+
function applyInlineReference(document, node, alias, field, schema, context) {
|
|
243
289
|
// Resolves for a schema that declares the referent (always so for the schema a
|
|
244
290
|
// type is projected through); a type framed against a foreign schema that
|
|
245
291
|
// omits it simply contributes no nesting.
|
|
@@ -249,7 +295,7 @@ function applyInlineReference(document, node, alias, field, schema) {
|
|
|
249
295
|
}
|
|
250
296
|
const referents = valuesOf(node, alias)
|
|
251
297
|
.filter(isObject)
|
|
252
|
-
.map((referent) => projectFields(referent, referenceType, schema))
|
|
298
|
+
.map((referent) => projectFields(referent, referenceType, schema, context))
|
|
253
299
|
// Fields, not identity, are what makes something a referent: a literal
|
|
254
300
|
// value object under the alias (dirty source data), or a node this
|
|
255
301
|
// reference type reads nothing from, projects nothing and is no referent.
|
package/dist/schema.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { ProjectedNode } from './project.js';
|
|
1
|
+
import type { ProjectedNode, ProjectionContext } 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
|
|
@@ -33,9 +33,13 @@ export declare function filterOperatorFor(kind: FieldKind): FilterOperator | und
|
|
|
33
33
|
* field with a {@link SearchFieldBase.derive `derive`} function instead of a
|
|
34
34
|
* `path` is a **derived field** – computed from the document projected so far
|
|
35
35
|
* rather than read from the graph – yet it still carries full query/schema/output
|
|
36
|
-
* behavior (e.g. `status`, the compatibility booleans). A
|
|
37
|
-
*
|
|
38
|
-
*
|
|
36
|
+
* behavior (e.g. `status`, the compatibility booleans). A `keyword`/`reference`
|
|
37
|
+
* field may instead declare {@link KeywordField.from `from`}, populating it from
|
|
38
|
+
* a {@link ProjectionValue} – something the run knows and the graph does not.
|
|
39
|
+
* `path`, `derive` and `from` are the three mutually exclusive value sources. A
|
|
40
|
+
* field declaring **no** role at all is an {@link isInternalField **internal
|
|
41
|
+
* field**}: projected so a later derive can read it, then pruned before the
|
|
42
|
+
* writer.
|
|
39
43
|
*
|
|
40
44
|
* The physical field names a declaration fans out to (per-locale search/sort
|
|
41
45
|
* keys) follow one convention, owned by
|
|
@@ -58,7 +62,10 @@ export interface SearchFieldBase {
|
|
|
58
62
|
/** Framed-IR predicate IRI to project from. Omit for a field populated by
|
|
59
63
|
* {@link SearchFieldBase.derive} (or outside the projection entirely). */
|
|
60
64
|
readonly path?: string;
|
|
61
|
-
/** Multi-valued.
|
|
65
|
+
/** Multi-valued: the field stores a list. Single-valued (the default) stores
|
|
66
|
+
* the FIRST value the graph carries, whatever their number – for every kind
|
|
67
|
+
* alike – so one declaration cannot mean a list to the projection and a
|
|
68
|
+
* scalar to the collection definition and the API. */
|
|
62
69
|
readonly array?: boolean;
|
|
63
70
|
/** Always present: a non-null scalar in the API output and
|
|
64
71
|
* a non-optional field in the engine index. Moot for arrays/booleans/`id`,
|
|
@@ -86,9 +93,30 @@ export interface SearchFieldBase {
|
|
|
86
93
|
* is the search document for a root type, and the referent’s own
|
|
87
94
|
* projected fields for a Reference Type – which carry an `id` only when the
|
|
88
95
|
* referent is a named node, never for a blank-node one.
|
|
96
|
+
*
|
|
97
|
+
* The second argument is the {@link ProjectionContext} – the run’s
|
|
98
|
+
* projection-time values, which are about the *indexing*, not the graph (the
|
|
99
|
+
* dataset being indexed). It is what lets a derive relate a projected value
|
|
100
|
+
* to its provenance: dropping a polymorphic `isPartOf` value that merely
|
|
101
|
+
* points back at the containing dataset, say.
|
|
89
102
|
*/
|
|
90
|
-
readonly derive?: (document: ProjectedNode) => unknown;
|
|
103
|
+
readonly derive?: (document: ProjectedNode, context: ProjectionContext) => unknown;
|
|
91
104
|
}
|
|
105
|
+
/**
|
|
106
|
+
* A projection-time value a field can be declared **over** – something the run
|
|
107
|
+
* knows that the graph does not say. `dataset` is the IRI of the dataset being
|
|
108
|
+
* indexed: every indexed document comes from exactly one, and for the many
|
|
109
|
+
* entity types that carry no containing-collection property (`Person`,
|
|
110
|
+
* `Organization`, `Place`, …) it is the only available answer to *which dataset
|
|
111
|
+
* does this come from*.
|
|
112
|
+
*
|
|
113
|
+
* A field declaring one is populated by the projection, like a `path`-bearing
|
|
114
|
+
* or `derive`d field, and carries the full range of behaviour: `output`,
|
|
115
|
+
* `filterable`, `facetable`, and – for a `reference` – a
|
|
116
|
+
* {@link ReferenceField.labelSource `labelSource`} that resolves the IRI to a
|
|
117
|
+
* readable label at query time.
|
|
118
|
+
*/
|
|
119
|
+
export type ProjectionValue = 'dataset';
|
|
92
120
|
/** Full-text inclusion with a `query_by` weight (folded; per-locale for
|
|
93
121
|
* localized text). Presence is what makes a field searchable. */
|
|
94
122
|
export interface Searchable {
|
|
@@ -124,6 +152,9 @@ export interface KeywordField extends SearchFieldBase, Searchable {
|
|
|
124
152
|
/** Projection-time value transform (e.g. strip a media-type prefix). */
|
|
125
153
|
readonly transform?: (value: string) => string;
|
|
126
154
|
readonly facetRanges?: never;
|
|
155
|
+
/** Populate from a {@link ProjectionValue} instead of the graph. Mutually
|
|
156
|
+
* exclusive with `path` and `derive`. */
|
|
157
|
+
readonly from?: ProjectionValue;
|
|
127
158
|
}
|
|
128
159
|
/** An IRI-valued reference to another entity, label-resolved at the surface. */
|
|
129
160
|
export interface ReferenceField extends SearchFieldBase, Searchable {
|
|
@@ -131,6 +162,11 @@ export interface ReferenceField extends SearchFieldBase, Searchable {
|
|
|
131
162
|
/** Projection-time value transform. */
|
|
132
163
|
readonly transform?: (value: string) => string;
|
|
133
164
|
readonly facetRanges?: never;
|
|
165
|
+
/** Populate from a {@link ProjectionValue} instead of the graph – a
|
|
166
|
+
* `dataset` reference is how a type declares the dataset it was indexed
|
|
167
|
+
* from, resolvable to a label like any other reference. Mutually exclusive
|
|
168
|
+
* with `path` and `derive`. */
|
|
169
|
+
readonly from?: ProjectionValue;
|
|
134
170
|
/**
|
|
135
171
|
* The `name` of the {@link SearchType} whose collection resolves this
|
|
136
172
|
* reference’s labels – its ‘label source’. The named type must declare an
|
|
@@ -318,6 +354,34 @@ export declare function isInlineReference(field: SearchField): field is Referenc
|
|
|
318
354
|
readonly strategy: 'inline';
|
|
319
355
|
};
|
|
320
356
|
};
|
|
357
|
+
/**
|
|
358
|
+
* The {@link ReferenceType} a **surfaced** inline reference nests – an
|
|
359
|
+
* `output` inline reference’s referent shape – or `undefined` for every other
|
|
360
|
+
* field. The one predicate the collection definition, result reconstruction and
|
|
361
|
+
* the API surfaces share, so they cannot disagree about which fields carry a
|
|
362
|
+
* nested Search Document and which carry a bare IRI: an inline reference
|
|
363
|
+
* declaring no Role is a reading device, pruned before the writer
|
|
364
|
+
* ({@link isInternalField}), and a `labelOnly`/`idOnly` reference stays an id
|
|
365
|
+
* (plus a resolved label).
|
|
366
|
+
*
|
|
367
|
+
* A schema built by {@link searchSchema} always resolves the reference type of
|
|
368
|
+
* its own types; `undefined` for a type declared against another schema.
|
|
369
|
+
*/
|
|
370
|
+
export declare function nestedReferenceType(schema: SearchSchema, field: SearchField): ReferenceType | undefined;
|
|
371
|
+
/**
|
|
372
|
+
* The Physical Field name a nested field carries in the engine: the surfaced
|
|
373
|
+
* inline reference’s own name, then the referent’s physical name –
|
|
374
|
+
* `media.contentUrl`, `media.label_nl`, and `media.thumbnail.contentUrl` one
|
|
375
|
+
* hop deeper. The nested counterpart of {@link physicalFields}, and equally the
|
|
376
|
+
* single home of its convention: the collection definition declares these
|
|
377
|
+
* names, so a second consumer (a nested filter compiler) reads them from here
|
|
378
|
+
* rather than restating the separator.
|
|
379
|
+
*
|
|
380
|
+
* The nested Search Document itself is keyed by the referent’s own field names
|
|
381
|
+
* ({@link ProjectedNode}) – the qualification is how an engine that stores
|
|
382
|
+
* nested documents flat addresses them, never what the projection writes.
|
|
383
|
+
*/
|
|
384
|
+
export declare function nestedFieldName(parent: string, name: string): string;
|
|
321
385
|
/**
|
|
322
386
|
* The framing depth a Root Type needs: how many hops the inline reference graph
|
|
323
387
|
* reaches from it (`Dataset → Subset → Measurement` is two), floored at one so
|
|
@@ -344,7 +408,7 @@ export declare function labelFieldOf(searchType: SearchType): TextField | undefi
|
|
|
344
408
|
*/
|
|
345
409
|
export interface SearchTypeIssue {
|
|
346
410
|
readonly field: string;
|
|
347
|
-
readonly reason: 'duplicate-field-name' | 'invalid-field-name' | 'unknown-kind' | 'invalid-locale' | 'missing-ref' | 'ref-not-allowed' | 'text-requires-locales' | 'locales-not-allowed' | 'facet-ranges-not-allowed' | 'searchable-not-allowed' | 'transform-not-allowed' | 'derive-with-path' | 'text-not-filterable' | 'text-not-facetable' | 'reserved-field-name';
|
|
411
|
+
readonly reason: 'duplicate-field-name' | 'invalid-field-name' | 'unknown-kind' | 'invalid-locale' | 'missing-ref' | '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' | 'reserved-field-name';
|
|
348
412
|
}
|
|
349
413
|
/**
|
|
350
414
|
* The reserved logical name of a document’s IRI – the one field every indexed
|
|
@@ -383,7 +447,13 @@ export declare const ID_FIELD = "id";
|
|
|
383
447
|
* - `transform` only on `keyword`/`reference` (the only kinds whose
|
|
384
448
|
* projection applies it);
|
|
385
449
|
* - `derive` and `path` are mutually exclusive (a field is projected or
|
|
386
|
-
* computed, never both)
|
|
450
|
+
* computed, never both);
|
|
451
|
+
* - `from` names a known {@link ProjectionValue}, sits on a `keyword`/`reference`
|
|
452
|
+
* field, excludes `path` and `derive` (the three are the value sources, and a
|
|
453
|
+
* field has exactly one), is not an `inline` reference (a projection value is
|
|
454
|
+
* a bare IRI, with no referent to carry fields from), and no two fields
|
|
455
|
+
* declare the same projection value – which would leave a consumer reading
|
|
456
|
+
* the dataset off a declaration no rule picks between.
|
|
387
457
|
*
|
|
388
458
|
* Pure and total: returns every issue rather than throwing;
|
|
389
459
|
* {@link assertValidSearchType} is the throwing entry point.
|
|
@@ -483,6 +553,18 @@ export declare function outputFields(searchType: SearchType): readonly SearchFie
|
|
|
483
553
|
export declare function isInternalField(field: SearchField): boolean;
|
|
484
554
|
/** Fields of kind `reference` (IRI-valued, label-resolved), in declaration order. */
|
|
485
555
|
export declare function referenceFields(searchType: SearchType): readonly ReferenceField[];
|
|
556
|
+
/**
|
|
557
|
+
* The field a type declares over the dataset being indexed
|
|
558
|
+
* ({@link ProjectionValue `from: 'dataset'`}), if it declares one.
|
|
559
|
+
* {@link validateSearchType} rejects a second field over the same projection
|
|
560
|
+
* value, so for any type that reached a {@link SearchSchema} this is the only
|
|
561
|
+
* such field rather than the first of several.
|
|
562
|
+
*
|
|
563
|
+
* The single lookup every consumer of the declaration shares – the projection
|
|
564
|
+
* that populates it, and the engine writer that keeps its provenance
|
|
565
|
+
* bookkeeping on it rather than on a private field of its own.
|
|
566
|
+
*/
|
|
567
|
+
export declare function datasetField(searchType: SearchType): (KeywordField | ReferenceField) | undefined;
|
|
486
568
|
/** Look up a field by its logical name. */
|
|
487
569
|
export declare function fieldNamed(searchType: SearchType, name: string): SearchField | undefined;
|
|
488
570
|
/**
|
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,aAAa,EAAE,MAAM,cAAc,CAAC;
|
|
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),
|
|
@@ -227,6 +316,15 @@ export const ID_FIELD = 'id';
|
|
|
227
316
|
const SEARCHABLE_KINDS = ['text', 'keyword', 'reference'];
|
|
228
317
|
/** Kinds whose projection applies the {@link KeywordField.transform}. */
|
|
229
318
|
const TRANSFORMABLE_KINDS = ['keyword', 'reference'];
|
|
319
|
+
/**
|
|
320
|
+
* Kinds a {@link ProjectionValue} can populate: every projection value is an
|
|
321
|
+
* IRI or a token, so only the two string-shaped kinds can hold one. `reference`
|
|
322
|
+
* is what carries a {@link ReferenceField.labelSource}, and so the kind a
|
|
323
|
+
* facet over the dataset wants.
|
|
324
|
+
*/
|
|
325
|
+
const FROM_KINDS = ['keyword', 'reference'];
|
|
326
|
+
/** The {@link ProjectionValue}s a declaration may name. */
|
|
327
|
+
const PROJECTION_VALUES = ['dataset'];
|
|
230
328
|
/**
|
|
231
329
|
* A safe logical field name: a GraphQL-style identifier. The name is
|
|
232
330
|
* interpolated raw into physical field names AND, for a display text field,
|
|
@@ -267,7 +365,13 @@ const LOCALE_PATTERN = /^[A-Za-z0-9]+(-[A-Za-z0-9]+)*$/;
|
|
|
267
365
|
* - `transform` only on `keyword`/`reference` (the only kinds whose
|
|
268
366
|
* projection applies it);
|
|
269
367
|
* - `derive` and `path` are mutually exclusive (a field is projected or
|
|
270
|
-
* computed, never both)
|
|
368
|
+
* computed, never both);
|
|
369
|
+
* - `from` names a known {@link ProjectionValue}, sits on a `keyword`/`reference`
|
|
370
|
+
* field, excludes `path` and `derive` (the three are the value sources, and a
|
|
371
|
+
* field has exactly one), is not an `inline` reference (a projection value is
|
|
372
|
+
* a bare IRI, with no referent to carry fields from), and no two fields
|
|
373
|
+
* declare the same projection value – which would leave a consumer reading
|
|
374
|
+
* the dataset off a declaration no rule picks between.
|
|
271
375
|
*
|
|
272
376
|
* Pure and total: returns every issue rather than throwing;
|
|
273
377
|
* {@link assertValidSearchType} is the throwing entry point.
|
|
@@ -275,6 +379,7 @@ const LOCALE_PATTERN = /^[A-Za-z0-9]+(-[A-Za-z0-9]+)*$/;
|
|
|
275
379
|
export function validateSearchType(searchType) {
|
|
276
380
|
const issues = [];
|
|
277
381
|
const seen = new Set();
|
|
382
|
+
const seenProjectionValues = new Set();
|
|
278
383
|
for (const declared of searchType.fields) {
|
|
279
384
|
// Validation guards declarations built OUTSIDE TypeScript (a SHACL
|
|
280
385
|
// generator, plain JS), so it inspects the uniform flat shape rather
|
|
@@ -350,6 +455,35 @@ export function validateSearchType(searchType) {
|
|
|
350
455
|
if (field.derive !== undefined && field.path !== undefined) {
|
|
351
456
|
issue('derive-with-path');
|
|
352
457
|
}
|
|
458
|
+
if (field.from !== undefined) {
|
|
459
|
+
if (!PROJECTION_VALUES.includes(field.from)) {
|
|
460
|
+
issue('unknown-projection-value');
|
|
461
|
+
}
|
|
462
|
+
else if (seenProjectionValues.has(field.from)) {
|
|
463
|
+
// Two fields over one projection value leaves every consumer that
|
|
464
|
+
// resolves the value back to a declaration – the writer’s provenance
|
|
465
|
+
// field, above all – with no rule to pick between them.
|
|
466
|
+
issue('duplicate-projection-value');
|
|
467
|
+
}
|
|
468
|
+
seenProjectionValues.add(field.from);
|
|
469
|
+
if (!FROM_KINDS.includes(field.kind)) {
|
|
470
|
+
issue('from-not-allowed');
|
|
471
|
+
}
|
|
472
|
+
if (field.path !== undefined) {
|
|
473
|
+
issue('from-with-path');
|
|
474
|
+
}
|
|
475
|
+
if (field.derive !== undefined) {
|
|
476
|
+
issue('from-with-derive');
|
|
477
|
+
}
|
|
478
|
+
// An inline reference carries a referent’s projected fields, and the
|
|
479
|
+
// collection definition declares it as a nested object. A projection
|
|
480
|
+
// value has no referent – it is a bare IRI – so the two together would
|
|
481
|
+
// declare an object field the projection fills with a string, and every
|
|
482
|
+
// document import would fail.
|
|
483
|
+
if (field.ref?.strategy === 'inline') {
|
|
484
|
+
issue('from-with-inline-ref');
|
|
485
|
+
}
|
|
486
|
+
}
|
|
353
487
|
}
|
|
354
488
|
return issues;
|
|
355
489
|
}
|
|
@@ -476,6 +610,21 @@ export function isInternalField(field) {
|
|
|
476
610
|
export function referenceFields(searchType) {
|
|
477
611
|
return searchType.fields.filter((field) => field.kind === 'reference');
|
|
478
612
|
}
|
|
613
|
+
/**
|
|
614
|
+
* The field a type declares over the dataset being indexed
|
|
615
|
+
* ({@link ProjectionValue `from: 'dataset'`}), if it declares one.
|
|
616
|
+
* {@link validateSearchType} rejects a second field over the same projection
|
|
617
|
+
* value, so for any type that reached a {@link SearchSchema} this is the only
|
|
618
|
+
* such field rather than the first of several.
|
|
619
|
+
*
|
|
620
|
+
* The single lookup every consumer of the declaration shares – the projection
|
|
621
|
+
* that populates it, and the engine writer that keeps its provenance
|
|
622
|
+
* bookkeeping on it rather than on a private field of its own.
|
|
623
|
+
*/
|
|
624
|
+
export function datasetField(searchType) {
|
|
625
|
+
return searchType.fields.find((field) => (field.kind === 'keyword' || field.kind === 'reference') &&
|
|
626
|
+
field.from === 'dataset');
|
|
627
|
+
}
|
|
479
628
|
/** Look up a field by its logical name. */
|
|
480
629
|
export function fieldNamed(searchType, name) {
|
|
481
630
|
return searchType.fields.find((field) => field.name === name);
|
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.16.0",
|
|
4
4
|
"description": "Engine- and domain-agnostic core for RDF-backed search: a unified declarative field model (SearchField/SearchType/SearchSchema), a neutral query IR, the SearchEngine port with logical result types, and a streaming CONSTRUCT-to-document projection. Bakes in no engine, protocol, or domain.",
|
|
5
5
|
"repository": {
|
|
6
6
|
"url": "git+https://github.com/ldelements/lde.git",
|
|
@@ -52,7 +52,7 @@
|
|
|
52
52
|
"devDependencies": {
|
|
53
53
|
"@tpluscode/rdf-ns-builders": "^5.0.0",
|
|
54
54
|
"@types/jsonld": "^1.5.15",
|
|
55
|
-
"n3": "^2.1.
|
|
55
|
+
"n3": "^2.1.2"
|
|
56
56
|
},
|
|
57
57
|
"peerDependencies": {
|
|
58
58
|
"vitest": "^4.0.0"
|