@lde/search 0.10.0 → 0.12.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/README.md +39 -9
- 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 +5 -5
- package/dist/engine.d.ts.map +1 -1
- package/dist/frame-by-type.d.ts +12 -6
- package/dist/frame-by-type.d.ts.map +1 -1
- package/dist/frame-by-type.js +46 -12
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/project.d.ts +8 -6
- package/dist/project.d.ts.map +1 -1
- package/dist/project.js +124 -37
- package/dist/schema.d.ts +110 -22
- package/dist/schema.d.ts.map +1 -1
- package/dist/schema.js +140 -9
- package/dist/testing.d.ts.map +1 -1
- package/dist/testing.js +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -71,11 +71,22 @@ Exports are stratified by audience:
|
|
|
71
71
|
The model has three levels, with analogues in SHACL ([one possible source](#why-a-declarative-model))
|
|
72
72
|
and GraphQL (one of the surfaces):
|
|
73
73
|
|
|
74
|
-
| Term | What it is
|
|
75
|
-
| -------------- |
|
|
76
|
-
| `SearchField` | One queryable field: a `kind`, the IR `path` it projects from, and the capability flags it opts into
|
|
77
|
-
| `SearchType` | One
|
|
78
|
-
| `SearchSchema` | The whole search declaration: every
|
|
74
|
+
| Term | What it is | SHACL | GraphQL |
|
|
75
|
+
| -------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------- | ----------- |
|
|
76
|
+
| `SearchField` | One queryable field: a `kind`, the IR `path` it projects from, and the capability flags it opts into | property shape | field |
|
|
77
|
+
| `SearchType` | One type’s complete declaration: its logical API `name` and fields (incl. derived). A **Root Type** declares a `class` (indexed, keys the schema); a **Reference Type** declares none (reached only through an inline reference) | NodeShape | object type |
|
|
78
|
+
| `SearchSchema` | The whole search declaration: every Root Type, keyed by `class` IRI, plus the Reference Types – build one with `searchSchema(...types)` | shapes graph | schema |
|
|
79
|
+
|
|
80
|
+
A `SearchType` is a **Root Type** or a **Reference Type**, told apart by one
|
|
81
|
+
absence: a Root Type declares a `class`, so roots are selected for it and a
|
|
82
|
+
writer opens a collection for it; a Reference Type declares none, so it is
|
|
83
|
+
never selected, framed by type or indexed – its identity is its name, and its
|
|
84
|
+
type comes from the edge that points at it. `searchSchema` partitions its
|
|
85
|
+
arguments accordingly: Root Types key the class map (`schema.values()` yields
|
|
86
|
+
only them, so no writer ever opens a collection for a Reference Type), Reference
|
|
87
|
+
Types go into a name index an inline `ref.typeName` resolves against. The
|
|
88
|
+
absence is load-bearing, and enforced at the type level – an indexed Reference
|
|
89
|
+
Type fails to compile ([ADR 11](../../docs/decisions/0011-decouple-rdf-depth-from-the-api-surface.md)).
|
|
79
90
|
|
|
80
91
|
`projectRoots` and the engine port each execute one `SearchType` at a time –
|
|
81
92
|
projection over the roots the pipeline selector supplied for that type; the
|
|
@@ -208,10 +219,29 @@ directly.
|
|
|
208
219
|
| `date` | `range` (inclusive) | yes | yes | ISO 8601 string (surface) |
|
|
209
220
|
| `boolean` | `is` | yes | – | boolean (absent = false) |
|
|
210
221
|
|
|
211
|
-
A `reference` carries `labelOnly`
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
222
|
+
A `reference` carries one of two strategies today: `labelOnly` (id + display
|
|
223
|
+
label, resolved at query time from a label source) and `inline` (the referent’s
|
|
224
|
+
own projected fields, carried inline). `idOnly` stays a forward declaration.
|
|
225
|
+
|
|
226
|
+
An **inline reference** resolves `ref.typeName` to a declared **Reference Type**
|
|
227
|
+
and projects the referent through it – a nested `SearchDocument`, or an array
|
|
228
|
+
for an `array` reference. Roles decide whether the nesting surfaces, so the same
|
|
229
|
+
construct serves two jobs (see
|
|
230
|
+
[ADR 11](../../docs/decisions/0011-decouple-rdf-depth-from-the-api-surface.md)):
|
|
231
|
+
|
|
232
|
+
- a **reading device** declares no role, so it is an internal field: projected
|
|
233
|
+
so a later `derive` can select and flatten a value a `path` cannot address (a
|
|
234
|
+
qualified hop), then pruned before the writer – nothing nested reaches the
|
|
235
|
+
engine or the API;
|
|
236
|
+
- an **API device** declares `output`, deliberately surfacing the nested
|
|
237
|
+
Reference Type.
|
|
238
|
+
|
|
239
|
+
So RDF depth and API shape stay independent: inline as deep as the source
|
|
240
|
+
demands, expose exactly the flat fields you want. Framing follows the inline
|
|
241
|
+
reference graph to the depth the schema declares (`Dataset → Subset →
|
|
242
|
+
Measurement` is two hops), and `searchSchema` rejects inline cycles – the one
|
|
243
|
+
way that depth could be unbounded – so it stays a bounded property of the
|
|
244
|
+
declaration.
|
|
215
245
|
|
|
216
246
|
A reference resolves its label from a **label source**: `labelSource` names
|
|
217
247
|
the `SearchType` whose collection holds the referenced entities. The named
|
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, displayFieldName, displayFieldPattern, displayLangOf, searchableFields, facetableFields, filterableFields, sortableFields, outputFields, isInternalField, referenceFields, fieldNamed, labelFieldOf, isRangeFacet, isoToUnixSeconds, unixSecondsToIso, } from './schema.js';
|
|
9
|
+
export { assertTypeInSchema, physicalFields, irAlias, displayFieldName, displayFieldPattern, displayLangOf, searchableFields, facetableFields, filterableFields, sortableFields, outputFields, isInternalField, isInlineReference, referenceFields, referenceTypeNamed, inlineFramingDepth, fieldNamed, 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, 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,gBAAgB,EAChB,mBAAmB,EACnB,aAAa,EACb,gBAAgB,EAChB,eAAe,EACf,gBAAgB,EAChB,cAAc,EACd,YAAY,EACZ,eAAe,EACf,eAAe,EACf,UAAU,EACV,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,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,eAAe,EACf,kBAAkB,EAClB,kBAAkB,EAClB,UAAU,EACV,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,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, displayFieldName, displayFieldPattern, displayLangOf, searchableFields, facetableFields, filterableFields, sortableFields, outputFields, isInternalField, referenceFields, fieldNamed, labelFieldOf, isRangeFacet, isoToUnixSeconds, unixSecondsToIso, } from './schema.js';
|
|
9
|
+
export { assertTypeInSchema, physicalFields, irAlias, displayFieldName, displayFieldPattern, displayLangOf, searchableFields, facetableFields, filterableFields, sortableFields, outputFields, isInternalField, isInlineReference, referenceFields, referenceTypeNamed, inlineFramingDepth, fieldNamed, labelFieldOf, isRangeFacet, isoToUnixSeconds, unixSecondsToIso, } from './schema.js';
|
|
10
10
|
export { physicalNameTokens } from './physical-name.js';
|
|
11
11
|
export { filterOperatorFor, filterOperator, validateQuery, assertValidQuery, pageForOffset, } from './query.js';
|
package/dist/engine.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { SearchQuery } from './query.js';
|
|
2
|
-
import type { SearchSchema, SearchType } from './schema.js';
|
|
2
|
+
import type { RootType, RootTypeOf, SearchSchema, SearchType } from './schema.js';
|
|
3
3
|
/**
|
|
4
4
|
* The engine port: the boundary a concrete engine adapter (e.g. the engine
|
|
5
5
|
* `@lde/search-typesense`’s `createTypesenseSearchEngine` returns) implements.
|
|
@@ -34,7 +34,7 @@ export interface SearchEngine<Types extends readonly SearchType[] = readonly Sea
|
|
|
34
34
|
/** The declaration this engine serves – exposed so a surface can route and
|
|
35
35
|
* a caller can enumerate the searchable types. */
|
|
36
36
|
readonly schema: SearchSchema<Types>;
|
|
37
|
-
search<T extends Types
|
|
37
|
+
search<T extends RootTypeOf<Types>>(searchType: T, query: SearchQuery): Promise<SearchResult<FacetFieldsOf<T>, OutputFieldsOf<T>>>;
|
|
38
38
|
/**
|
|
39
39
|
* The batch entry point: answer several facet-only queries against one type
|
|
40
40
|
* in a single engine round-trip (where the engine supports one, e.g.
|
|
@@ -52,7 +52,7 @@ export interface SearchEngine<Types extends readonly SearchType[] = readonly Sea
|
|
|
52
52
|
* transport itself). The port contract holds for every query in the batch;
|
|
53
53
|
* an empty `queries` resolves to `[]` without touching the engine.
|
|
54
54
|
*/
|
|
55
|
-
searchFacets<T extends Types
|
|
55
|
+
searchFacets<T extends RootTypeOf<Types>>(searchType: T, queries: readonly SearchQuery[]): Promise<readonly FacetsOutcome<FacetFieldsOf<T>>[]>;
|
|
56
56
|
}
|
|
57
57
|
/**
|
|
58
58
|
* One query’s outcome in a {@link SearchEngine.searchFacets} batch – its
|
|
@@ -84,7 +84,7 @@ export type FacetMap<FacetField extends string = string> = Readonly<Partial<Reco
|
|
|
84
84
|
* `: SearchType` annotation degrades to `string` (its field names are not
|
|
85
85
|
* statically known).
|
|
86
86
|
*/
|
|
87
|
-
export type FacetFieldsOf<Type extends SearchType> =
|
|
87
|
+
export type FacetFieldsOf<Type extends SearchType> = RootType extends Type ? string : Extract<Type['fields'][number], {
|
|
88
88
|
readonly facetable: true;
|
|
89
89
|
}>['name'];
|
|
90
90
|
/**
|
|
@@ -92,7 +92,7 @@ export type FacetFieldsOf<Type extends SearchType> = SearchType extends Type ? s
|
|
|
92
92
|
* can hold. Like {@link FacetFieldsOf}, degrades to `string` for a widened
|
|
93
93
|
* declaration.
|
|
94
94
|
*/
|
|
95
|
-
export type OutputFieldsOf<Type extends SearchType> =
|
|
95
|
+
export type OutputFieldsOf<Type extends SearchType> = RootType extends Type ? string : Extract<Type['fields'][number], {
|
|
96
96
|
readonly output: true;
|
|
97
97
|
}>['name'];
|
|
98
98
|
/**
|
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,
|
|
1
|
+
{"version":3,"file":"engine.d.ts","sourceRoot":"","sources":["../src/engine.ts"],"names":[],"mappings":"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"}
|
package/dist/frame-by-type.d.ts
CHANGED
|
@@ -27,15 +27,21 @@ export interface SubjectIndex {
|
|
|
27
27
|
export declare function buildSubjectIndex(quads: Iterable<Quad>): SubjectIndex;
|
|
28
28
|
/**
|
|
29
29
|
* Frame each of the given `roots` from a prebuilt {@link SubjectIndex} into one
|
|
30
|
-
* JSON-LD node. Each root subject’s own triples plus the
|
|
31
|
-
*
|
|
32
|
-
*
|
|
33
|
-
*
|
|
34
|
-
* `@context`, so framed keys
|
|
30
|
+
* JSON-LD node. Each root subject’s own triples plus the nodes it references –
|
|
31
|
+
* to `depth` hops (e.g. nested publisher/distribution resources at one hop, an
|
|
32
|
+
* inline reference’s referents deeper) – are framed one subject at a time, so
|
|
33
|
+
* beyond the shared subject index only a single subgraph is held (whole-graph
|
|
34
|
+
* `jsonld.frame()` is ~O(N²)). The frame carries no `@context`, so framed keys
|
|
35
|
+
* are full predicate IRIs.
|
|
36
|
+
*
|
|
37
|
+
* `depth` is the number of reference hops to embed; it defaults to one (the
|
|
38
|
+
* long-standing single-hop embed) and comes from the schema for a type with
|
|
39
|
+
* inline references (`inlineFramingDepth`). Bounded per batch, so the O(N²)
|
|
40
|
+
* framing cost applies to a batch of roots, not the graph (ADR 12).
|
|
35
41
|
*
|
|
36
42
|
* The roots are supplied explicitly rather than discovered from `rdf:type`: the
|
|
37
43
|
* caller ({@link projectRoots}, from the pipeline selector) already holds them
|
|
38
44
|
* and passes them directly. A root absent from the index simply frames nothing.
|
|
39
45
|
*/
|
|
40
|
-
export declare function frameSubjects(index: SubjectIndex, roots: readonly string[]): AsyncIterable<FramedNode>;
|
|
46
|
+
export declare function frameSubjects(index: SubjectIndex, roots: readonly string[], depth?: number): AsyncIterable<FramedNode>;
|
|
41
47
|
//# sourceMappingURL=frame-by-type.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"frame-by-type.d.ts","sourceRoot":"","sources":["../src/frame-by-type.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,cAAc,CAAC;AAGzC,4EAA4E;AAC5E,MAAM,MAAM,UAAU,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;AAIjD;;;;;;GAMG;AACH,MAAM,WAAW,YAAY;IAC3B,4EAA4E;IAC5E,QAAQ,CAAC,SAAS,EAAE,WAAW,CAAC,MAAM,EAAE,SAAS,IAAI,EAAE,CAAC,CAAC;CAC1D;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,QAAQ,CAAC,IAAI,CAAC,GAAG,YAAY,CAkBrE;AAED
|
|
1
|
+
{"version":3,"file":"frame-by-type.d.ts","sourceRoot":"","sources":["../src/frame-by-type.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,cAAc,CAAC;AAGzC,4EAA4E;AAC5E,MAAM,MAAM,UAAU,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;AAIjD;;;;;;GAMG;AACH,MAAM,WAAW,YAAY;IAC3B,4EAA4E;IAC5E,QAAQ,CAAC,SAAS,EAAE,WAAW,CAAC,MAAM,EAAE,SAAS,IAAI,EAAE,CAAC,CAAC;CAC1D;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,QAAQ,CAAC,IAAI,CAAC,GAAG,YAAY,CAkBrE;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAuB,aAAa,CAClC,KAAK,EAAE,YAAY,EACnB,KAAK,EAAE,SAAS,MAAM,EAAE,EACxB,KAAK,SAAI,GACR,aAAa,CAAC,UAAU,CAAC,CAqB3B"}
|
package/dist/frame-by-type.js
CHANGED
|
@@ -34,25 +34,26 @@ export function buildSubjectIndex(quads) {
|
|
|
34
34
|
}
|
|
35
35
|
/**
|
|
36
36
|
* Frame each of the given `roots` from a prebuilt {@link SubjectIndex} into one
|
|
37
|
-
* JSON-LD node. Each root subject’s own triples plus the
|
|
38
|
-
*
|
|
39
|
-
*
|
|
40
|
-
*
|
|
41
|
-
* `@context`, so framed keys
|
|
37
|
+
* JSON-LD node. Each root subject’s own triples plus the nodes it references –
|
|
38
|
+
* to `depth` hops (e.g. nested publisher/distribution resources at one hop, an
|
|
39
|
+
* inline reference’s referents deeper) – are framed one subject at a time, so
|
|
40
|
+
* beyond the shared subject index only a single subgraph is held (whole-graph
|
|
41
|
+
* `jsonld.frame()` is ~O(N²)). The frame carries no `@context`, so framed keys
|
|
42
|
+
* are full predicate IRIs.
|
|
43
|
+
*
|
|
44
|
+
* `depth` is the number of reference hops to embed; it defaults to one (the
|
|
45
|
+
* long-standing single-hop embed) and comes from the schema for a type with
|
|
46
|
+
* inline references (`inlineFramingDepth`). Bounded per batch, so the O(N²)
|
|
47
|
+
* framing cost applies to a batch of roots, not the graph (ADR 12).
|
|
42
48
|
*
|
|
43
49
|
* The roots are supplied explicitly rather than discovered from `rdf:type`: the
|
|
44
50
|
* caller ({@link projectRoots}, from the pipeline selector) already holds them
|
|
45
51
|
* and passes them directly. A root absent from the index simply frames nothing.
|
|
46
52
|
*/
|
|
47
|
-
export async function* frameSubjects(index, roots) {
|
|
53
|
+
export async function* frameSubjects(index, roots, depth = 1) {
|
|
48
54
|
const { bySubject } = index;
|
|
49
55
|
for (const rootIri of roots) {
|
|
50
|
-
const
|
|
51
|
-
const referenced = owned
|
|
52
|
-
.filter((quad) => quad.object.termType === 'NamedNode' ||
|
|
53
|
-
quad.object.termType === 'BlankNode')
|
|
54
|
-
.flatMap((quad) => bySubject.get(quad.object.value) ?? []);
|
|
55
|
-
const subgraph = [...owned, ...referenced];
|
|
56
|
+
const subgraph = collectSubgraph(bySubject, rootIri, depth);
|
|
56
57
|
const expanded = await jsonld.fromRDF(subgraph);
|
|
57
58
|
// Frame for THIS specific root subject by its `@id`. The subgraph embeds the
|
|
58
59
|
// root’s one-hop references, and such a referent can itself be one of the
|
|
@@ -67,3 +68,36 @@ export async function* frameSubjects(index, roots) {
|
|
|
67
68
|
}
|
|
68
69
|
}
|
|
69
70
|
}
|
|
71
|
+
/**
|
|
72
|
+
* Collect a root subject’s triples plus those of every subject it references
|
|
73
|
+
* within `depth` hops, breadth-first. Each subject is visited once (so a
|
|
74
|
+
* reference cycle in the data terminates and no subject’s triples are
|
|
75
|
+
* duplicated); `jsonld.frame` embeds whatever the subgraph reaches. Bounded by
|
|
76
|
+
* `depth`, so only the reachable subgraph of one root is materialized at a time.
|
|
77
|
+
*/
|
|
78
|
+
function collectSubgraph(bySubject, rootIri, depth) {
|
|
79
|
+
const collected = [];
|
|
80
|
+
const visited = new Set();
|
|
81
|
+
let frontier = [rootIri];
|
|
82
|
+
for (let hop = 0; hop <= depth; hop++) {
|
|
83
|
+
const next = [];
|
|
84
|
+
for (const subject of frontier) {
|
|
85
|
+
if (visited.has(subject)) {
|
|
86
|
+
continue;
|
|
87
|
+
}
|
|
88
|
+
visited.add(subject);
|
|
89
|
+
const owned = bySubject.get(subject) ?? [];
|
|
90
|
+
collected.push(...owned);
|
|
91
|
+
if (hop < depth) {
|
|
92
|
+
for (const quad of owned) {
|
|
93
|
+
if (quad.object.termType === 'NamedNode' ||
|
|
94
|
+
quad.object.termType === 'BlankNode') {
|
|
95
|
+
next.push(quad.object.value);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
frontier = next;
|
|
101
|
+
}
|
|
102
|
+
return collected;
|
|
103
|
+
}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
export { projectRoots } from './project.js';
|
|
2
2
|
export type { SearchDocument } from './project.js';
|
|
3
3
|
export { defineSearchType, searchSchema, validateSearchType, assertValidSearchType, } from './schema.js';
|
|
4
|
-
export type { FieldKind, SearchField, SearchFieldBase, TextField, KeywordField, ReferenceField, NumericField, BooleanField, SearchType, 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, } from './schema.js';
|
|
5
5
|
export type { SearchQuery, Filter, Sort } from './query.js';
|
|
6
6
|
export type { SearchEngine, SearchResult, SearchHit, ResultDocument, SearchValue, LocalizedValue, Reference, FacetBucket, FacetMap, FacetsOutcome, FacetFieldsOf, OutputFieldsOf, } from './engine.js';
|
|
7
7
|
export type { FramedNode } from './frame-by-type.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,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AAKnD,OAAO,EACL,gBAAgB,EAChB,YAAY,EACZ,kBAAkB,EAClB,qBAAqB,GACtB,MAAM,aAAa,CAAC;AACrB,YAAY,EACV,SAAS,EACT,WAAW,EACX,eAAe,EACf,SAAS,EACT,YAAY,EACZ,cAAc,EACd,YAAY,EACZ,YAAY,EACZ,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,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,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AAKnD,OAAO,EACL,gBAAgB,EAChB,YAAY,EACZ,kBAAkB,EAClB,qBAAqB,GACtB,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,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 SearchSchema, type SearchType } from './schema.js';
|
|
3
|
+
import { type RootType, type SearchSchema, type SearchType } from './schema.js';
|
|
4
4
|
/** A flat search document. `id` is the engine document key. */
|
|
5
5
|
export type SearchDocument = {
|
|
6
6
|
id: string;
|
|
@@ -13,11 +13,13 @@ export type SearchDocument = {
|
|
|
13
13
|
* statement of what the projection reads. {@link isInternalField Internal
|
|
14
14
|
* fields} (those declaring no role) are populated so a later derive can read
|
|
15
15
|
* them, then pruned before the document is returned: they must reach neither a
|
|
16
|
-
* writer nor the collection definition.
|
|
17
|
-
*
|
|
18
|
-
*
|
|
16
|
+
* writer nor the collection definition. Pruning ({@link pruneInternalFields})
|
|
17
|
+
* recurses into the referents of surfaced inline references, so that invariant
|
|
18
|
+
* holds at every depth, not just the root. The physical field names a field
|
|
19
|
+
* fans out to come from {@link physicalFields}, the single source shared with
|
|
20
|
+
* the engine collection definition and the query compiler.
|
|
19
21
|
*/
|
|
20
|
-
export declare function projectDocument(node: FramedNode, searchType: SearchType): SearchDocument;
|
|
22
|
+
export declare function projectDocument(node: FramedNode, searchType: SearchType, schema?: SearchSchema): SearchDocument;
|
|
21
23
|
/**
|
|
22
24
|
* Project a single type over a known set of `roots` – the per-type, roots-given
|
|
23
25
|
* projection. The roots are supplied by the caller (the pipeline selector)
|
|
@@ -31,5 +33,5 @@ export declare function projectDocument(node: FramedNode, searchType: SearchType
|
|
|
31
33
|
* Consumes `quads` once, so it accepts any `Iterable` – a batch’s materialized
|
|
32
34
|
* array or a chained generator merging several readers.
|
|
33
35
|
*/
|
|
34
|
-
export declare function projectRoots(quads: Iterable<Quad>, roots: readonly string[], schema: SearchSchema, searchType:
|
|
36
|
+
export declare function projectRoots(quads: Iterable<Quad>, roots: readonly string[], schema: SearchSchema, searchType: RootType): AsyncIterable<SearchDocument>;
|
|
35
37
|
//# 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,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,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;AAEtE;;;;;;;;;;;;;GAaG;AACH,wBAAgB,eAAe,CAC7B,IAAI,EAAE,UAAU,EAChB,UAAU,EAAE,UAAU,EACtB,MAAM,CAAC,EAAE,YAAY,GACpB,cAAc,CAIhB;AAiED;;;;;;;;;;;;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
|
@@ -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, isInternalField, isoToUnixSeconds, physicalFields, } from './schema.js';
|
|
3
|
+
import { assertTypeInSchema, displayFieldName, inlineFramingDepth, irAlias, isInternalField, isInlineReference, isoToUnixSeconds, physicalFields, referenceTypeNamed, } from './schema.js';
|
|
4
4
|
/**
|
|
5
5
|
* Project one framed JSON-LD node into a flat search document: apply each field
|
|
6
6
|
* of the type in declaration order. A field with a `derive` function computes
|
|
@@ -9,26 +9,65 @@ import { assertTypeInSchema, displayFieldName, isInternalField, isoToUnixSeconds
|
|
|
9
9
|
* statement of what the projection reads. {@link isInternalField Internal
|
|
10
10
|
* fields} (those declaring no role) are populated so a later derive can read
|
|
11
11
|
* them, then pruned before the document is returned: they must reach neither a
|
|
12
|
-
* writer nor the collection definition.
|
|
13
|
-
*
|
|
14
|
-
*
|
|
12
|
+
* writer nor the collection definition. Pruning ({@link pruneInternalFields})
|
|
13
|
+
* recurses into the referents of surfaced inline references, so that invariant
|
|
14
|
+
* holds at every depth, not just the root. The physical field names a field
|
|
15
|
+
* fans out to come from {@link physicalFields}, the single source shared with
|
|
16
|
+
* the engine collection definition and the query compiler.
|
|
15
17
|
*/
|
|
16
|
-
export function projectDocument(node, searchType) {
|
|
18
|
+
export function projectDocument(node, searchType, schema) {
|
|
19
|
+
const document = projectFields(node, searchType, schema);
|
|
20
|
+
pruneInternalFields(document, searchType, schema);
|
|
21
|
+
return document;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Prune every internal field from a fully projected document, in place. Runs
|
|
25
|
+
* only after all projection – so every derive that might read an internal
|
|
26
|
+
* field, at any depth, has already run – which makes this single post-order
|
|
27
|
+
* pass safe. A no-role inline reference is itself internal, so it is deleted
|
|
28
|
+
* whole here; a surfaced (`output`) inline reference survives, but its own
|
|
29
|
+
* internal helper fields are pruned from the nested document. That keeps the
|
|
30
|
+
* *a field without a role reaches neither the engine nor the API* invariant
|
|
31
|
+
* true at every depth of the reference graph, not just at the root.
|
|
32
|
+
*/
|
|
33
|
+
function pruneInternalFields(document, searchType, schema) {
|
|
34
|
+
for (const field of searchType.fields) {
|
|
35
|
+
if (isInternalField(field)) {
|
|
36
|
+
delete document[field.name];
|
|
37
|
+
continue;
|
|
38
|
+
}
|
|
39
|
+
// A surfaced inline reference nests its referent(s) as SearchDocument(s);
|
|
40
|
+
// prune those too, by their reference type.
|
|
41
|
+
if (schema === undefined || field.kind !== 'reference') {
|
|
42
|
+
continue;
|
|
43
|
+
}
|
|
44
|
+
const ref = field.ref;
|
|
45
|
+
if (ref?.strategy !== 'inline') {
|
|
46
|
+
continue;
|
|
47
|
+
}
|
|
48
|
+
const referenceType = referenceTypeNamed(schema, ref.typeName);
|
|
49
|
+
const nested = document[field.name];
|
|
50
|
+
if (referenceType === undefined || nested === undefined) {
|
|
51
|
+
continue;
|
|
52
|
+
}
|
|
53
|
+
for (const referent of Array.isArray(nested) ? nested : [nested]) {
|
|
54
|
+
pruneInternalFields(referent, referenceType, schema);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
/** Apply every field of `searchType` to a fresh document, without pruning – the
|
|
59
|
+
* shared core of {@link projectDocument} and inline-referent projection.
|
|
60
|
+
* Pruning is deferred to a single recursive pass ({@link pruneInternalFields})
|
|
61
|
+
* once the whole nested structure is projected, so a derive at any depth can
|
|
62
|
+
* still read an internal field before it is removed. */
|
|
63
|
+
function projectFields(node, searchType, schema) {
|
|
17
64
|
const id = node['@id'];
|
|
18
65
|
if (typeof id !== 'string') {
|
|
19
|
-
throw new Error(`Cannot project a
|
|
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.`);
|
|
20
67
|
}
|
|
21
68
|
const document = { id };
|
|
22
69
|
for (const field of searchType.fields) {
|
|
23
|
-
applyField(document, node, field);
|
|
24
|
-
}
|
|
25
|
-
// Prune internal fields: they were projected only so the derives above could
|
|
26
|
-
// read them (a structural memo shared across every derive that needs the
|
|
27
|
-
// value), and must not reach the writer or the collection definition.
|
|
28
|
-
for (const field of searchType.fields) {
|
|
29
|
-
if (isInternalField(field)) {
|
|
30
|
-
delete document[field.name];
|
|
31
|
-
}
|
|
70
|
+
applyField(document, node, field, searchType, schema);
|
|
32
71
|
}
|
|
33
72
|
return document;
|
|
34
73
|
}
|
|
@@ -52,11 +91,12 @@ export async function* projectRoots(quads, roots, schema, searchType) {
|
|
|
52
91
|
// non-`DISTINCT` `SELECT` over a one-to-many join yields the same subject per
|
|
53
92
|
// matched row – and a repeated root would otherwise frame and emit a
|
|
54
93
|
// duplicate document under the same `id`.
|
|
55
|
-
|
|
56
|
-
|
|
94
|
+
const depth = inlineFramingDepth(schema, searchType);
|
|
95
|
+
for await (const node of frameSubjects(index, [...new Set(roots)], depth)) {
|
|
96
|
+
yield projectDocument(node, searchType, schema);
|
|
57
97
|
}
|
|
58
98
|
}
|
|
59
|
-
function applyField(document, node, field) {
|
|
99
|
+
function applyField(document, node, field, searchType, schema) {
|
|
60
100
|
if (field.derive !== undefined) {
|
|
61
101
|
const value = field.derive(document);
|
|
62
102
|
if (value !== undefined) {
|
|
@@ -64,29 +104,45 @@ function applyField(document, node, field) {
|
|
|
64
104
|
}
|
|
65
105
|
return;
|
|
66
106
|
}
|
|
67
|
-
|
|
68
|
-
if (path === undefined) {
|
|
107
|
+
if (field.path === undefined) {
|
|
69
108
|
// Neither path nor derive: populated outside the projection, if at all.
|
|
70
109
|
return;
|
|
71
110
|
}
|
|
111
|
+
// The framed node is keyed by the {@link irAlias IR Alias} the extraction
|
|
112
|
+
// CONSTRUCT minted, not by the source `path`: `path` states what to read from
|
|
113
|
+
// the graph (the reader adapter’s grammar), the alias is what the reader
|
|
114
|
+
// emitted it under. Minting against `searchType` – a root field against the
|
|
115
|
+
// root type, an inline referent’s field against its reference type – is what
|
|
116
|
+
// lets one subject be a root of two types without their fields colliding.
|
|
117
|
+
const alias = irAlias(searchType, field);
|
|
118
|
+
if (isInlineReference(field)) {
|
|
119
|
+
// An inline reference is a nested structure, not a bare IRI: it can only be
|
|
120
|
+
// projected with the schema that declares its reference type. Without one,
|
|
121
|
+
// project nothing rather than fall through and emit the referent IRIs under
|
|
122
|
+
// the field name (the wrong shape).
|
|
123
|
+
if (schema !== undefined) {
|
|
124
|
+
applyInlineReference(document, node, alias, field, schema);
|
|
125
|
+
}
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
72
128
|
switch (field.kind) {
|
|
73
129
|
case 'text':
|
|
74
|
-
return applyText(document, langValuesOf(node,
|
|
130
|
+
return applyText(document, langValuesOf(node, alias), field);
|
|
75
131
|
case 'keyword':
|
|
76
|
-
return applyFacet(document, literalsOf(node,
|
|
132
|
+
return applyFacet(document, literalsOf(node, alias), field);
|
|
77
133
|
case 'reference':
|
|
78
|
-
return applyFacet(document, irisOf(node,
|
|
134
|
+
return applyFacet(document, irisOf(node, alias), field);
|
|
79
135
|
case 'integer':
|
|
80
|
-
return setNumber(document, field.name, toInteger(firstLiteralOf(node,
|
|
136
|
+
return setNumber(document, field.name, toInteger(firstLiteralOf(node, alias)));
|
|
81
137
|
case 'number':
|
|
82
|
-
return setNumber(document, field.name, toNumber(firstLiteralOf(node,
|
|
138
|
+
return setNumber(document, field.name, toNumber(firstLiteralOf(node, alias)));
|
|
83
139
|
case 'date': {
|
|
84
|
-
const literal = firstLiteralOf(node,
|
|
140
|
+
const literal = firstLiteralOf(node, alias);
|
|
85
141
|
return setNumber(document, field.name, literal === undefined ? undefined : isoToUnixSeconds(literal));
|
|
86
142
|
}
|
|
87
143
|
case 'boolean': {
|
|
88
144
|
// The xsd:boolean lexical space: true/false/1/0.
|
|
89
|
-
const literal = firstLiteralOf(node,
|
|
145
|
+
const literal = firstLiteralOf(node, alias);
|
|
90
146
|
if (literal !== undefined) {
|
|
91
147
|
document[field.name] = literal === 'true' || literal === '1';
|
|
92
148
|
}
|
|
@@ -153,26 +209,57 @@ function applyFacet(document, raw, field) {
|
|
|
153
209
|
setArray(document, physicalFields(field).search[0], dedupe(values.map((value) => fold(value))));
|
|
154
210
|
}
|
|
155
211
|
}
|
|
156
|
-
|
|
157
|
-
|
|
212
|
+
/**
|
|
213
|
+
* Project an inline reference: the referent node(s) embedded under the field’s
|
|
214
|
+
* {@link irAlias IR Alias} are each projected through the reference’s
|
|
215
|
+
* {@link ReferenceType} (whose own fields read their own aliases, minted against
|
|
216
|
+
* the reference type) and attached under the field’s name – a nested
|
|
217
|
+
* {@link SearchDocument} for a single reference, an array for an `array` one.
|
|
218
|
+
* The referent is projected in full – internal fields included – so the
|
|
219
|
+
* declaring type’s (or the reference type’s own) derives can read them;
|
|
220
|
+
* {@link pruneInternalFields} then removes the internal fields from a *surfaced*
|
|
221
|
+
* referent and deletes an internal inline reference whole. Recurses through
|
|
222
|
+
* `schema`, so an inline reference may itself carry further inline references to
|
|
223
|
+
* the schema’s declared depth. The referent type is guaranteed declared by
|
|
224
|
+
* {@link searchSchema}.
|
|
225
|
+
*/
|
|
226
|
+
function applyInlineReference(document, node, alias, field, schema) {
|
|
227
|
+
// Resolves for a schema that declares the referent (always so for the schema a
|
|
228
|
+
// type is projected through); a type framed against a foreign schema that
|
|
229
|
+
// omits it simply contributes no nesting.
|
|
230
|
+
const referenceType = referenceTypeNamed(schema, field.ref.typeName);
|
|
231
|
+
if (referenceType === undefined) {
|
|
232
|
+
return;
|
|
233
|
+
}
|
|
234
|
+
const referents = valuesOf(node, alias)
|
|
235
|
+
.filter(isObject)
|
|
236
|
+
.filter((referent) => typeof referent['@id'] === 'string')
|
|
237
|
+
.map((referent) => projectFields(referent, referenceType, schema));
|
|
238
|
+
if (referents.length === 0) {
|
|
239
|
+
return;
|
|
240
|
+
}
|
|
241
|
+
document[field.name] = field.array === true ? referents : referents[0];
|
|
242
|
+
}
|
|
243
|
+
function langValuesOf(node, key) {
|
|
244
|
+
return valuesOf(node, key)
|
|
158
245
|
.map(toLangValue)
|
|
159
246
|
.filter((value) => value !== undefined);
|
|
160
247
|
}
|
|
161
|
-
function literalsOf(node,
|
|
162
|
-
return valuesOf(node,
|
|
248
|
+
function literalsOf(node, key) {
|
|
249
|
+
return valuesOf(node, key)
|
|
163
250
|
.map(literalString)
|
|
164
251
|
.filter((value) => value !== undefined);
|
|
165
252
|
}
|
|
166
|
-
function firstLiteralOf(node,
|
|
167
|
-
return literalsOf(node,
|
|
253
|
+
function firstLiteralOf(node, key) {
|
|
254
|
+
return literalsOf(node, key)[0];
|
|
168
255
|
}
|
|
169
|
-
function irisOf(node,
|
|
170
|
-
return valuesOf(node,
|
|
256
|
+
function irisOf(node, key) {
|
|
257
|
+
return valuesOf(node, key)
|
|
171
258
|
.map(iriString)
|
|
172
259
|
.filter((value) => value !== undefined);
|
|
173
260
|
}
|
|
174
|
-
function valuesOf(node,
|
|
175
|
-
const value = node[
|
|
261
|
+
function valuesOf(node, key) {
|
|
262
|
+
const value = node[key];
|
|
176
263
|
if (value === undefined) {
|
|
177
264
|
return [];
|
|
178
265
|
}
|
package/dist/schema.d.ts
CHANGED
|
@@ -194,26 +194,60 @@ export interface FacetRange {
|
|
|
194
194
|
readonly min?: number;
|
|
195
195
|
readonly max?: number;
|
|
196
196
|
}
|
|
197
|
-
/**
|
|
198
|
-
|
|
199
|
-
* `class` IRI its documents are instances of (the RDF class), and the queryable
|
|
200
|
-
* `fields` (including {@link SearchField.derive derived} ones). A SHACL
|
|
201
|
-
* generator can emit one per NodeShape (`name`←`sh:name`/local name,
|
|
202
|
-
* `class`←`sh:targetClass`, `fields`←its property shapes), but that is a source,
|
|
203
|
-
* not a requirement.
|
|
204
|
-
*/
|
|
205
|
-
export interface SearchType {
|
|
197
|
+
/** The declaration members every {@link SearchType} shares. */
|
|
198
|
+
export interface SearchTypeBase {
|
|
206
199
|
/** Logical API name (PascalCase, e.g. `Dataset`) – names the type in the API
|
|
207
200
|
* surfaces (GraphQL type names, a REST path), the way each field’s
|
|
208
201
|
* {@link SearchField.name} names that field. Deliberately declared rather
|
|
209
202
|
* than derived from the `class` IRI, so re-modelling the vocabulary cannot
|
|
210
203
|
* silently rename the public contract. */
|
|
211
204
|
readonly name: string;
|
|
205
|
+
readonly fields: readonly SearchField[];
|
|
206
|
+
}
|
|
207
|
+
/**
|
|
208
|
+
* A **Root Type**: a {@link SearchType} that is indexed. It declares a `class`,
|
|
209
|
+
* roots are selected for it, a Writer owns a collection for it, and the
|
|
210
|
+
* {@link SearchSchema} is keyed by it. A SHACL generator can emit one per
|
|
211
|
+
* NodeShape (`name`←`sh:name`/local name, `class`←`sh:targetClass`,
|
|
212
|
+
* `fields`←its property shapes), but that is a source, not a requirement.
|
|
213
|
+
*/
|
|
214
|
+
export interface RootType extends SearchTypeBase {
|
|
212
215
|
/** The RDF class IRI its documents are instances of (`sh:targetClass`); the
|
|
213
|
-
* key a {@link SearchSchema} maps this type under.
|
|
216
|
+
* key a {@link SearchSchema} maps this type under. Its presence is what makes
|
|
217
|
+
* a type a Root Type – and so what gives it a collection. */
|
|
214
218
|
readonly class: string;
|
|
215
|
-
readonly fields: readonly SearchField[];
|
|
216
219
|
}
|
|
220
|
+
/**
|
|
221
|
+
* A **Reference Type**: a {@link SearchType} reached only through an
|
|
222
|
+
* {@link ReferenceField.ref inline reference}. It declares **no `class`** –
|
|
223
|
+
* never selected, never framed by type, never indexed; its identity is its
|
|
224
|
+
* `name`, and its type comes from the edge that points at it, not from the node.
|
|
225
|
+
* The absence is load-bearing, not stylistic: a `class` would put it in the
|
|
226
|
+
* {@link SearchSchema} map and silently earn it a collection nobody asked for.
|
|
227
|
+
* The shape an inline reference carries – see
|
|
228
|
+
* [ADR 11](../../docs/decisions/0011-decouple-rdf-depth-from-the-api-surface.md).
|
|
229
|
+
*/
|
|
230
|
+
export interface ReferenceType extends SearchTypeBase {
|
|
231
|
+
/** A Reference Type declares no class; declaring one makes it a
|
|
232
|
+
* {@link RootType}. Typed as `never` so the two shapes discriminate the way
|
|
233
|
+
* {@link SearchField} discriminates by `kind`: an indexed Reference Type
|
|
234
|
+
* fails to compile, not at run time. */
|
|
235
|
+
readonly class?: never;
|
|
236
|
+
}
|
|
237
|
+
/**
|
|
238
|
+
* One type’s complete search declaration: its logical API `name`, the queryable
|
|
239
|
+
* `fields` (including {@link SearchField.derive derived} ones), and – for a
|
|
240
|
+
* {@link RootType} – the RDF `class` its documents are instances of. Either a
|
|
241
|
+
* Root Type or a {@link ReferenceType}; the absence of a `class` tells them
|
|
242
|
+
* apart.
|
|
243
|
+
*/
|
|
244
|
+
export type SearchType = RootType | ReferenceType;
|
|
245
|
+
/** The Root Types among a declared tuple – the ones a {@link SearchSchema}
|
|
246
|
+
* keys and a Writer opens a collection for. Reference Types are excluded, so
|
|
247
|
+
* no consumer that iterates `schema.values()` ever meets one. */
|
|
248
|
+
export type RootTypeOf<Types extends readonly SearchType[]> = Extract<Types[number], {
|
|
249
|
+
readonly class: string;
|
|
250
|
+
}>;
|
|
217
251
|
/**
|
|
218
252
|
* Declare a {@link SearchType}, capturing it as a literal: the `const` type
|
|
219
253
|
* parameter preserves the field names and capability flags that the type-level
|
|
@@ -224,31 +258,67 @@ export interface SearchType {
|
|
|
224
258
|
*/
|
|
225
259
|
export declare function defineSearchType<const Type extends SearchType>(searchType: Type): Type;
|
|
226
260
|
/**
|
|
227
|
-
* The complete search declaration of a deployment: every
|
|
228
|
-
* keyed by its `class` IRI
|
|
229
|
-
*
|
|
230
|
-
*
|
|
261
|
+
* The complete search declaration of a deployment: every {@link RootType},
|
|
262
|
+
* keyed by its `class` IRI, plus the {@link ReferenceType}s an inline reference
|
|
263
|
+
* resolves against. Build one with {@link searchSchema}, which captures the
|
|
264
|
+
* declared types as a literal tuple (`Types`), so schema-bound consumers (the
|
|
265
|
+
* engine port) can type their per-type behaviour off it. A plain
|
|
231
266
|
* `: SearchSchema` annotation widens gracefully to `SearchType`.
|
|
267
|
+
*
|
|
268
|
+
* `values()` yields only Root Types – Reference Types are held apart in a name
|
|
269
|
+
* index ({@link referenceTypeNamed}), so a Writer that opens one collection per
|
|
270
|
+
* `values()` entry can never open one for a Reference Type.
|
|
232
271
|
*/
|
|
233
272
|
/** Brand for {@link SearchSchema}: type-only, no runtime existence. Makes the
|
|
234
273
|
* schema NOMINAL – a hand-built `Map` is not assignable, so `searchSchema()`
|
|
235
274
|
* (which validates) is the only way to obtain one and downstream consumers
|
|
236
275
|
* need no defensive re-validation. */
|
|
237
276
|
export declare const validSearchSchema: unique symbol;
|
|
238
|
-
export interface SearchSchema<Types extends readonly SearchType[] = readonly SearchType[]> extends ReadonlyMap<string, Types
|
|
277
|
+
export interface SearchSchema<Types extends readonly SearchType[] = readonly SearchType[]> extends ReadonlyMap<string, RootTypeOf<Types>> {
|
|
239
278
|
readonly [validSearchSchema]: true;
|
|
240
279
|
}
|
|
241
280
|
/**
|
|
242
|
-
* Build a {@link SearchSchema} from
|
|
281
|
+
* Build a {@link SearchSchema} from type declarations. Its arguments are
|
|
282
|
+
* **partitioned**: {@link RootType}s (those declaring a `class`) key the map –
|
|
283
|
+
* so a Writer opens exactly one collection per Root Type – and
|
|
284
|
+
* {@link ReferenceType}s (no `class`) go into a name index that an inline
|
|
285
|
+
* `ref.typeName` resolves against ({@link referenceTypeNamed}).
|
|
243
286
|
*
|
|
244
287
|
* Every declaration is validated ({@link assertValidSearchType}) – the
|
|
245
288
|
* declaration-time counterpart of the port’s `assertValidQuery` – and the
|
|
246
|
-
* schema-wide invariants are enforced: no two
|
|
247
|
-
* (they would silently overwrite each other in the map)
|
|
248
|
-
* key the API surfaces
|
|
249
|
-
*
|
|
289
|
+
* schema-wide invariants are enforced: no two Root Types may share a `class` IRI
|
|
290
|
+
* (they would silently overwrite each other in the map) and no two types may
|
|
291
|
+
* share a `name` (names key the API surfaces, across Root and Reference Types
|
|
292
|
+
* alike). Every inline reference must resolve to a declared Reference Type, and
|
|
293
|
+
* the inline reference graph must be acyclic – the only way its depth could be
|
|
294
|
+
* unbounded. Throws on the first invalid declaration, so a bad schema fails at
|
|
295
|
+
* startup, not per document at index time or per query.
|
|
250
296
|
*/
|
|
251
297
|
export declare function searchSchema<const Types extends readonly SearchType[]>(...types: Types): SearchSchema<Types>;
|
|
298
|
+
/**
|
|
299
|
+
* The {@link ReferenceType} an inline `ref.typeName` names, or `undefined` when
|
|
300
|
+
* the schema declares none by that name. The read side of the name index
|
|
301
|
+
* {@link searchSchema} partitions the Reference Types into – the projection
|
|
302
|
+
* resolves an inline reference’s referent shape through it.
|
|
303
|
+
*/
|
|
304
|
+
export declare function referenceTypeNamed(schema: SearchSchema, name: string): ReferenceType | undefined;
|
|
305
|
+
/** Whether a field is an inline reference – a {@link ReferenceField} whose
|
|
306
|
+
* `ref` carries its referent’s projected fields ({@link ReferenceType}). */
|
|
307
|
+
export declare function isInlineReference(field: SearchField): field is ReferenceField & {
|
|
308
|
+
readonly ref: {
|
|
309
|
+
readonly typeName: string;
|
|
310
|
+
readonly strategy: 'inline';
|
|
311
|
+
};
|
|
312
|
+
};
|
|
313
|
+
/**
|
|
314
|
+
* The framing depth a Root Type needs: how many hops the inline reference graph
|
|
315
|
+
* reaches from it (`Dataset → Subset → Measurement` is two), floored at one so
|
|
316
|
+
* the existing single-hop embed for non-inline references is preserved. Depth is
|
|
317
|
+
* a property of the declaration, bounded because {@link searchSchema} rejects
|
|
318
|
+
* inline cycles – never a knob or a constant. Framing bounded per batch keeps
|
|
319
|
+
* memory bounded by the unit of work (ADR 12), not the graph.
|
|
320
|
+
*/
|
|
321
|
+
export declare function inlineFramingDepth(schema: SearchSchema, searchType: SearchType): number;
|
|
252
322
|
/**
|
|
253
323
|
* The text field a label source serves labels from – the ‘label’ convention
|
|
254
324
|
* in one place: an `output` (something to reconstruct a label from),
|
|
@@ -304,7 +374,7 @@ export declare function validateSearchType(searchType: SearchType): readonly Sea
|
|
|
304
374
|
* meet an index the deployment did not declare. Identity-based: the exact
|
|
305
375
|
* declaration object must be in the schema, not a lookalike.
|
|
306
376
|
*/
|
|
307
|
-
export declare function assertTypeInSchema(schema: SearchSchema, searchType:
|
|
377
|
+
export declare function assertTypeInSchema(schema: SearchSchema, searchType: RootType): void;
|
|
308
378
|
/** Throw on a structurally invalid {@link SearchType} ({@link validateSearchType}),
|
|
309
379
|
* naming every issue. Called by {@link searchSchema} for each declaration. */
|
|
310
380
|
export declare function assertValidSearchType(searchType: SearchType): void;
|
|
@@ -394,6 +464,24 @@ export declare function isInternalField(field: SearchField): boolean;
|
|
|
394
464
|
export declare function referenceFields(searchType: SearchType): readonly ReferenceField[];
|
|
395
465
|
/** Look up a field by its logical name. */
|
|
396
466
|
export declare function fieldNamed(searchType: SearchType, name: string): SearchField | undefined;
|
|
467
|
+
/**
|
|
468
|
+
* The **IR Alias** predicate for a field: `urn:lde:‹SearchType.name›/‹field.name›`.
|
|
469
|
+
* The extraction CONSTRUCT emits a field’s value under this minted predicate, and
|
|
470
|
+
* the {@link projectDocument projection} reads it back under the same key – the
|
|
471
|
+
* two sides agree by calling this one function rather than by a hand-written
|
|
472
|
+
* convention that can drift (exactly the argument {@link physicalFields}’ JSDoc
|
|
473
|
+
* makes for the physical fanout).
|
|
474
|
+
*
|
|
475
|
+
* A property path cannot be a CONSTRUCT template verb, so flattening a multi-hop
|
|
476
|
+
* value onto its subject must mint a predicate for it; that predicate is a
|
|
477
|
+
* mechanical function of the field name, never authored by hand and never a
|
|
478
|
+
* public vocabulary. Field names are unique per type and restricted to
|
|
479
|
+
* `[A-Za-z_][A-Za-z0-9_]*` ({@link validateSearchType}), so the alias needs no
|
|
480
|
+
* escaping; it is qualified by the **type** name because one subject can be a
|
|
481
|
+
* root of two types (`frame-by-type`), which must not collide on a shared field
|
|
482
|
+
* name.
|
|
483
|
+
*/
|
|
484
|
+
export declare function irAlias(searchType: SearchType, field: SearchField): string;
|
|
397
485
|
/**
|
|
398
486
|
* Whether a facet on this field returns fixed range bins (a histogram) rather
|
|
399
487
|
* than one bucket per distinct value: it declares non-empty
|
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
|
@@ -28,32 +28,143 @@ export function defineSearchType(searchType) {
|
|
|
28
28
|
return searchType;
|
|
29
29
|
}
|
|
30
30
|
/**
|
|
31
|
-
*
|
|
31
|
+
* The Reference Type name index each {@link SearchSchema} carries alongside its
|
|
32
|
+
* class-keyed root map, kept out of the map itself so no consumer iterating
|
|
33
|
+
* `values()` ever meets a Reference Type. Held in a `WeakMap` rather than a
|
|
34
|
+
* property, so the schema stays a plain branded `Map` and the index is read only
|
|
35
|
+
* through {@link referenceTypeNamed}.
|
|
36
|
+
*/
|
|
37
|
+
const referenceTypesBySchema = new WeakMap();
|
|
38
|
+
/** Whether a declared type is a {@link RootType} (declares a `class`) rather
|
|
39
|
+
* than a {@link ReferenceType}. */
|
|
40
|
+
function isRootType(searchType) {
|
|
41
|
+
return searchType.class !== undefined;
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Build a {@link SearchSchema} from type declarations. Its arguments are
|
|
45
|
+
* **partitioned**: {@link RootType}s (those declaring a `class`) key the map –
|
|
46
|
+
* so a Writer opens exactly one collection per Root Type – and
|
|
47
|
+
* {@link ReferenceType}s (no `class`) go into a name index that an inline
|
|
48
|
+
* `ref.typeName` resolves against ({@link referenceTypeNamed}).
|
|
32
49
|
*
|
|
33
50
|
* Every declaration is validated ({@link assertValidSearchType}) – the
|
|
34
51
|
* declaration-time counterpart of the port’s `assertValidQuery` – and the
|
|
35
|
-
* schema-wide invariants are enforced: no two
|
|
36
|
-
* (they would silently overwrite each other in the map)
|
|
37
|
-
* key the API surfaces
|
|
38
|
-
*
|
|
52
|
+
* schema-wide invariants are enforced: no two Root Types may share a `class` IRI
|
|
53
|
+
* (they would silently overwrite each other in the map) and no two types may
|
|
54
|
+
* share a `name` (names key the API surfaces, across Root and Reference Types
|
|
55
|
+
* alike). Every inline reference must resolve to a declared Reference Type, and
|
|
56
|
+
* the inline reference graph must be acyclic – the only way its depth could be
|
|
57
|
+
* unbounded. Throws on the first invalid declaration, so a bad schema fails at
|
|
58
|
+
* startup, not per document at index time or per query.
|
|
39
59
|
*/
|
|
40
60
|
export function searchSchema(...types) {
|
|
41
61
|
const typeIris = new Set();
|
|
42
62
|
const names = new Set();
|
|
43
63
|
for (const searchType of types) {
|
|
44
64
|
assertValidSearchType(searchType);
|
|
45
|
-
if (
|
|
46
|
-
|
|
65
|
+
if (isRootType(searchType)) {
|
|
66
|
+
if (typeIris.has(searchType.class)) {
|
|
67
|
+
throw new Error(`Duplicate search type IRI “${searchType.class}”; each Root Type must declare a distinct class.`);
|
|
68
|
+
}
|
|
69
|
+
typeIris.add(searchType.class);
|
|
47
70
|
}
|
|
48
71
|
if (names.has(searchType.name)) {
|
|
49
72
|
throw new Error(`Duplicate search type name “${searchType.name}”; each SearchType must declare a distinct name.`);
|
|
50
73
|
}
|
|
51
|
-
typeIris.add(searchType.class);
|
|
52
74
|
names.add(searchType.name);
|
|
53
75
|
}
|
|
76
|
+
const referenceTypes = new Map(types
|
|
77
|
+
.filter((searchType) => !isRootType(searchType))
|
|
78
|
+
.map((searchType) => [searchType.name, searchType]));
|
|
79
|
+
assertResolvableInlineReferences(types, referenceTypes);
|
|
54
80
|
assertResolvableLabelSources(types);
|
|
55
81
|
// The one blessed cast: only this validated constructor mints the brand.
|
|
56
|
-
|
|
82
|
+
const schema = new Map(types
|
|
83
|
+
.filter(isRootType)
|
|
84
|
+
.map((searchType) => [searchType.class, searchType]));
|
|
85
|
+
referenceTypesBySchema.set(schema, referenceTypes);
|
|
86
|
+
return schema;
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* The {@link ReferenceType} an inline `ref.typeName` names, or `undefined` when
|
|
90
|
+
* the schema declares none by that name. The read side of the name index
|
|
91
|
+
* {@link searchSchema} partitions the Reference Types into – the projection
|
|
92
|
+
* resolves an inline reference’s referent shape through it.
|
|
93
|
+
*/
|
|
94
|
+
export function referenceTypeNamed(schema, name) {
|
|
95
|
+
return referenceTypesBySchema.get(schema)?.get(name);
|
|
96
|
+
}
|
|
97
|
+
/** Whether a field is an inline reference – a {@link ReferenceField} whose
|
|
98
|
+
* `ref` carries its referent’s projected fields ({@link ReferenceType}). */
|
|
99
|
+
export function isInlineReference(field) {
|
|
100
|
+
return field.kind === 'reference' && field.ref?.strategy === 'inline';
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* The framing depth a Root Type needs: how many hops the inline reference graph
|
|
104
|
+
* reaches from it (`Dataset → Subset → Measurement` is two), floored at one so
|
|
105
|
+
* the existing single-hop embed for non-inline references is preserved. Depth is
|
|
106
|
+
* a property of the declaration, bounded because {@link searchSchema} rejects
|
|
107
|
+
* inline cycles – never a knob or a constant. Framing bounded per batch keeps
|
|
108
|
+
* memory bounded by the unit of work (ADR 12), not the graph.
|
|
109
|
+
*/
|
|
110
|
+
export function inlineFramingDepth(schema, searchType) {
|
|
111
|
+
return Math.max(1, inlineChainLength(schema, searchType));
|
|
112
|
+
}
|
|
113
|
+
/** The longest inline reference chain reachable from `searchType`. The recursion
|
|
114
|
+
* terminates because {@link searchSchema} rejects inline cycles; a reference the
|
|
115
|
+
* given schema does not declare (e.g. a type framed against another schema)
|
|
116
|
+
* contributes no depth. */
|
|
117
|
+
function inlineChainLength(schema, searchType) {
|
|
118
|
+
let longest = 0;
|
|
119
|
+
for (const field of searchType.fields) {
|
|
120
|
+
if (!isInlineReference(field)) {
|
|
121
|
+
continue;
|
|
122
|
+
}
|
|
123
|
+
const referent = referenceTypeNamed(schema, field.ref.typeName);
|
|
124
|
+
if (referent === undefined) {
|
|
125
|
+
continue;
|
|
126
|
+
}
|
|
127
|
+
longest = Math.max(longest, 1 + inlineChainLength(schema, referent));
|
|
128
|
+
}
|
|
129
|
+
return longest;
|
|
130
|
+
}
|
|
131
|
+
/**
|
|
132
|
+
* Every inline `ref.typeName` must resolve to a **declared Reference Type**, and
|
|
133
|
+
* the inline reference graph must be acyclic. Unlike a `labelOnly` reference –
|
|
134
|
+
* whose `typeName` is just an API name – an inline reference carries its
|
|
135
|
+
* referent’s fields, so it must know their shape, and an inline cycle is the one
|
|
136
|
+
* way framing depth could be unbounded. Checked schema-wide, because a single
|
|
137
|
+
* declaration cannot see its siblings.
|
|
138
|
+
*/
|
|
139
|
+
function assertResolvableInlineReferences(types, referenceTypes) {
|
|
140
|
+
for (const searchType of types) {
|
|
141
|
+
for (const field of searchType.fields) {
|
|
142
|
+
if (!isInlineReference(field)) {
|
|
143
|
+
continue;
|
|
144
|
+
}
|
|
145
|
+
if (!referenceTypes.has(field.ref.typeName)) {
|
|
146
|
+
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
|
+
}
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
for (const referenceType of referenceTypes.values()) {
|
|
151
|
+
assertNoInlineCycle(referenceType, referenceTypes, new Set());
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
function assertNoInlineCycle(referenceType, referenceTypes, onPath) {
|
|
155
|
+
if (onPath.has(referenceType.name)) {
|
|
156
|
+
throw new Error(`Inline reference cycle through reference type “${referenceType.name}”; an inline reference graph must be acyclic, so its framing depth stays bounded.`);
|
|
157
|
+
}
|
|
158
|
+
const extended = new Set([...onPath, referenceType.name]);
|
|
159
|
+
for (const field of referenceType.fields) {
|
|
160
|
+
if (!isInlineReference(field)) {
|
|
161
|
+
continue;
|
|
162
|
+
}
|
|
163
|
+
// Resolvability is validated before any cycle check, so every inline
|
|
164
|
+
// `typeName` here names a declared reference type.
|
|
165
|
+
const referent = referenceTypes.get(field.ref.typeName);
|
|
166
|
+
assertNoInlineCycle(referent, referenceTypes, extended);
|
|
167
|
+
}
|
|
57
168
|
}
|
|
58
169
|
/**
|
|
59
170
|
* The text field a label source serves labels from – the ‘label’ convention
|
|
@@ -344,6 +455,26 @@ export function referenceFields(searchType) {
|
|
|
344
455
|
export function fieldNamed(searchType, name) {
|
|
345
456
|
return searchType.fields.find((field) => field.name === name);
|
|
346
457
|
}
|
|
458
|
+
/**
|
|
459
|
+
* The **IR Alias** predicate for a field: `urn:lde:‹SearchType.name›/‹field.name›`.
|
|
460
|
+
* The extraction CONSTRUCT emits a field’s value under this minted predicate, and
|
|
461
|
+
* the {@link projectDocument projection} reads it back under the same key – the
|
|
462
|
+
* two sides agree by calling this one function rather than by a hand-written
|
|
463
|
+
* convention that can drift (exactly the argument {@link physicalFields}’ JSDoc
|
|
464
|
+
* makes for the physical fanout).
|
|
465
|
+
*
|
|
466
|
+
* A property path cannot be a CONSTRUCT template verb, so flattening a multi-hop
|
|
467
|
+
* value onto its subject must mint a predicate for it; that predicate is a
|
|
468
|
+
* mechanical function of the field name, never authored by hand and never a
|
|
469
|
+
* public vocabulary. Field names are unique per type and restricted to
|
|
470
|
+
* `[A-Za-z_][A-Za-z0-9_]*` ({@link validateSearchType}), so the alias needs no
|
|
471
|
+
* escaping; it is qualified by the **type** name because one subject can be a
|
|
472
|
+
* root of two types (`frame-by-type`), which must not collide on a shared field
|
|
473
|
+
* name.
|
|
474
|
+
*/
|
|
475
|
+
export function irAlias(searchType, field) {
|
|
476
|
+
return `urn:lde:${searchType.name}/${field.name}`;
|
|
477
|
+
}
|
|
347
478
|
/**
|
|
348
479
|
* Whether a facet on this field returns fixed range bins (a histogram) rather
|
|
349
480
|
* than one bucket per distinct value: it declares non-empty
|
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;AAShD;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,4BAA4B,CAC1C,IAAI,EAAE,MAAM,EACZ,MAAM,EAAE,MAAM,YAAY,GACzB,IAAI,CAsJN"}
|
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
|
|
3
|
+
import { facetableFields, filterableFields } 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
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lde/search",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.12.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",
|