@lde/search 0.8.1 → 0.10.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 CHANGED
@@ -29,11 +29,11 @@ It provides four things:
29
29
  out of it, so the two cannot drift;
30
30
  - **engine port** – `SearchEngine` and the logical result types
31
31
  (`SearchResult` / `SearchHit` / `ResultDocument` / `Reference` / …);
32
- - **streaming projection** – `projectGraph`, RDF `CONSTRUCT` quads → flat
33
- search documents.
32
+ - **streaming projection** – `projectRoots`, RDF `CONSTRUCT` quads → flat
33
+ search documents, one root type at a time.
34
34
 
35
35
  ```
36
- SearchSchema ─┬─► projection (projectGraph → flat documents) [here]
36
+ SearchSchema ─┬─► projection (projectRoots → flat documents) [here]
37
37
  ├─► engine adapter (collection definition + query compiler) e.g. @lde/search-typesense
38
38
  ├─► query semantics (SearchQuery, filter/sort/facet) [here]
39
39
  └─► API surface (GraphQL / REST) e.g. @lde/search-api-graphql
@@ -58,8 +58,7 @@ a typed, deterministic function – easy to test, and swappable per deployment.
58
58
  Exports are stratified by audience:
59
59
 
60
60
  - **`@lde/search`** – the authoring surface: `defineSearchType`,
61
- `searchSchema`, `projectGraph` (+ the IR readers `derive` functions use),
62
- validation, and every model/query/result type.
61
+ `searchSchema`, `projectRoots`, validation, and every model/query/result type.
63
62
  - **`@lde/search/adapter`** – plumbing for engine adapters and API surfaces:
64
63
  `physicalFields`, the field selectors, `assertValidQuery`, the filter
65
64
  operators and storage codecs.
@@ -78,8 +77,9 @@ and GraphQL (one of the surfaces):
78
77
  | `SearchType` | One root type’s complete declaration: its logical API `name`, its `class` IRI and its fields (incl. derived) | NodeShape | object type |
79
78
  | `SearchSchema` | The whole search declaration: every `SearchType`, keyed by `class` IRI – build one with `searchSchema(...types)` | shapes graph | schema |
80
79
 
81
- `projectGraph` and the GraphQL surface consume a `SearchSchema` (projecting
82
- every type in one pass); the engine port executes one `SearchType` at a time.
80
+ `projectRoots` and the engine port each execute one `SearchType` at a time –
81
+ projection over the roots the pipeline selector supplied for that type; the
82
+ GraphQL surface consumes the whole `SearchSchema`.
83
83
 
84
84
  ### API conventions
85
85
 
@@ -100,20 +100,19 @@ Two conventions hold across the whole family:
100
100
  ## Field model
101
101
 
102
102
  The mapping is data, not code. Each field declares its `kind`, the IR `path` to
103
- read (or a `derive` function for a **derived** field, computed from the framed
104
- node in declaration order – so it may read fields declared before it), and the
105
- capabilities it opts into. The physical field names a declaration fans out to
106
- (per-locale search/sort keys) come from
107
- `physicalFields`, the single convention projection, the collection definition and the
108
- query compiler all share.
103
+ read (or a `derive` function for a **derived** field, computed from the document
104
+ in declaration order – so it may read fields declared before it, never the
105
+ graph), and the capabilities (**roles**) it opts into. `path` is therefore the
106
+ complete statement of what the projection reads from the graph. A field that
107
+ declares **no** role is an **internal field**: projected so a later `derive` can
108
+ read it, then pruned before the writer and absent from the collection definition
109
+ – not stored, not indexed, no RAM. The physical field names a declaration fans
110
+ out to (per-locale search/sort keys) come from `physicalFields`, the single
111
+ convention projection, the collection definition and the query compiler all
112
+ share.
109
113
 
110
114
  ```ts
111
- import {
112
- defineSearchType,
113
- projectGraph,
114
- irisOf,
115
- searchSchema,
116
- } from '@lde/search';
115
+ import { defineSearchType, projectRoots, searchSchema } from '@lde/search';
117
116
 
118
117
  const DATASET = defineSearchType({
119
118
  name: 'Dataset', // logical API name: names the GraphQL type, a REST path, …
@@ -140,24 +139,27 @@ const DATASET = defineSearchType({
140
139
  },
141
140
  // → size (int)
142
141
  { name: 'size', path: 'urn:dr:size', kind: 'integer', sortable: true },
143
- // derived field (no path): computed from the framed node
142
+ // internal field (no role): projected as a reading device for the derive
143
+ // below, then pruned before the writer – absent from the collection too
144
+ { name: 'classes', path: 'urn:dr:class', kind: 'reference' },
145
+ // derived field (no path): computed from the document in declaration order,
146
+ // never from the graph – so `path` stays the whole statement of what is read
144
147
  {
145
148
  name: 'classCount',
146
149
  kind: 'integer',
147
150
  sortable: true,
148
- derive: (node) => irisOf(node, 'urn:dr:class').length,
151
+ derive: (document) =>
152
+ (document.classes as string[] | undefined)?.length ?? 0,
149
153
  },
150
154
  ],
151
155
  });
152
156
 
153
- for await (const { searchType, document } of projectGraph(
154
- quads,
155
- searchSchema(DATASET),
156
- )) {
157
- // one flat search document per matching subject, streamed and tagged with the
158
- // SearchType it was framed from — so a multi-collection writer can route each
159
- // document to the collection for its type. A single-collection consumer just
160
- // reads `document`.
157
+ const schema = searchSchema(DATASET);
158
+ for await (const document of projectRoots(quads, roots, schema, DATASET)) {
159
+ // one flat search document per given root subject, streamed. The caller (the
160
+ // pipeline selector) supplies `roots`; pairing a document with its type for a
161
+ // multi-collection writer is the pipeline glue’s job (see
162
+ // `@lde/search-pipeline`), not the projection’s.
161
163
  }
162
164
  ```
163
165
 
@@ -219,31 +221,39 @@ source fails at startup. A reference without a `labelSource` stays id-only.
219
221
 
220
222
  ## Projection
221
223
 
222
- `projectGraph` is fully streaming: subjects are grouped and framed one at a time
223
- and documents are yielded as produced, so beyond a subject index memory stays
224
- flat at scale (framing the whole graph at once is roughly O(N²)). Duplicate
225
- triples are collapsed first, because some SPARQL engines (e.g. QLever) do not
226
- deduplicate `CONSTRUCT` output. The IR carries no `@context`, so a `derive` function
227
- reading it sees full predicate IRIs with language tags preserved.
228
-
229
- Each yielded value is a `{ searchType, document }` pair: the whole schema
230
- projects into **one** mixed stream, and every document is tagged with the
231
- `SearchType` it was framed from. That tag is what lets the write side fan the
232
- stream out to per-type collections without re-deriving the type from the
233
- document – see `@lde/search-pipeline`’s multi-collection writer.
234
-
235
- `projectGraph` **consumes the quads once** – a single scan builds the subject
236
- index every type frames off – and so accepts any `Iterable<Quad>`, not just a
237
- materialized array. A caller merging several sources can pass a chained
224
+ `projectRoots` projects **one root type** over the roots the caller supplies –
225
+ the pipeline selector already holds them, so nothing is discovered from
226
+ `rdf:type` and a `CONSTRUCT` need emit no type triple. It is fully streaming:
227
+ each root’s subgraph is framed one at a time and its document yielded as
228
+ produced, so beyond a subject index memory stays flat at scale (framing the
229
+ whole graph at once is roughly O(N²)). Duplicate triples are collapsed first,
230
+ because some SPARQL engines (e.g. QLever) do not deduplicate `CONSTRUCT` output.
231
+ Every predicate a value comes from is read through a field’s `path`; a `derive`
232
+ computes only from the document projected so far, so `path` is the whole
233
+ statement of what the projection reads. `assertTypeInSchema` guards that
234
+ the passed `SearchType` is a member of the schema – the port’s own membership
235
+ check – so no schema is ever forged to scope a projection to one type.
236
+
237
+ `projectRoots` yields a **bare** `SearchDocument`. Pairing each document with the
238
+ `SearchType` it belongs to, so the write side can fan a mixed stream out to
239
+ per-type collections, is a routing concern owned by the pipeline glue – see
240
+ `@lde/search-pipeline`’s `searchStages` and multi-collection writer – not the
241
+ projection. One stage per root type keeps `@lde/search` pipeline-free.
242
+
243
+ `projectRoots` **consumes the quads once** – a single scan builds the subject
244
+ index the roots frame off – and so accepts any `Iterable<Quad>`, not just a
245
+ materialized array. A caller merging several readers can pass a chained
238
246
  generator instead of building a third full array at the projection peak:
239
247
 
240
248
  ```ts
241
- projectGraph(
249
+ projectRoots(
242
250
  (function* () {
243
251
  yield* registerQuads;
244
252
  yield* dkgQuads;
245
253
  })(),
254
+ roots,
246
255
  schema,
256
+ DATASET,
247
257
  );
248
258
  ```
249
259
 
@@ -307,7 +317,7 @@ unrepresentable; the port enforces them for everyone else (deployment
307
317
  ## Typed results
308
318
 
309
319
  An engine is **bound to the whole `SearchSchema` at construction** – like
310
- every other schema consumer (`projectGraph(quads, schema)`,
320
+ every other schema consumer (`projectRoots(quads, roots, schema, type)`,
311
321
  `buildGraphQLSchema(schema)`): the adapter factory takes the deployment’s
312
322
  declaration, so a query can never meet the wrong index, and deployment-level
313
323
  concerns (the label cache, cross-type search, facet batching) have one home.
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, referenceFields, fieldNamed, labelFieldOf, isRangeFacet, isoToUnixSeconds, unixSecondsToIso, } from './schema.js';
9
+ export { assertTypeInSchema, physicalFields, displayFieldName, displayFieldPattern, displayLangOf, searchableFields, facetableFields, filterableFields, sortableFields, outputFields, isInternalField, referenceFields, 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';
@@ -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,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,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"}
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, referenceFields, fieldNamed, labelFieldOf, isRangeFacet, isoToUnixSeconds, unixSecondsToIso, } from './schema.js';
9
+ export { assertTypeInSchema, physicalFields, displayFieldName, displayFieldPattern, displayLangOf, searchableFields, facetableFields, filterableFields, sortableFields, outputFields, isInternalField, referenceFields, 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
@@ -3,10 +3,10 @@ import type { 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.
6
- * An engine is **bound to the whole {@link SearchSchema} at construction** —
6
+ * An engine is **bound to the whole {@link SearchSchema} at construction** –
7
7
  * the adapter factory takes the deployment’s declaration together with the
8
8
  * physical location of every type (its collections map), mirroring the other
9
- * schema consumers (`projectGraph(quads, schema)`,
9
+ * schema consumers (`projectRoots(quads, roots, schema, type)`,
10
10
  * `buildGraphQLSchema(schema)`): everything is a function of the schema. A
11
11
  * query can never meet the wrong index, deployment-level concerns (label
12
12
  * cache, cross-type search, facet batching) have one home, and a search names
@@ -19,8 +19,8 @@ import type { SearchSchema, SearchType } from './schema.js';
19
19
  *
20
20
  * Port contract: an adapter ALWAYS rejects a `searchType` that is not in its
21
21
  * bound schema, and ALWAYS validates the incoming query against it
22
- * (`assertValidQuery`) — unknown or non-filterable fields, mismatched
23
- * operators, unknown facets — rather than passing garbage to its engine.
22
+ * (`assertValidQuery`) – unknown or non-filterable fields, mismatched
23
+ * operators, unknown facets – rather than passing garbage to its engine.
24
24
  * Validation is not the caller’s job: it must hold for every surface and for
25
25
  * injected deployment policy.
26
26
  *
@@ -31,7 +31,7 @@ import type { SearchSchema, SearchType } from './schema.js';
31
31
  * gracefully to string keys ({@link FacetFieldsOf}).
32
32
  */
33
33
  export interface SearchEngine<Types extends readonly SearchType[] = readonly SearchType[]> {
34
- /** The declaration this engine serves — exposed so a surface can route and
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
37
  search<T extends Types[number]>(searchType: T, query: SearchQuery): Promise<SearchResult<FacetFieldsOf<T>, OutputFieldsOf<T>>>;
@@ -78,7 +78,7 @@ export interface SearchResult<FacetField extends string = string, OutputField ex
78
78
  */
79
79
  export type FacetMap<FacetField extends string = string> = Readonly<Partial<Record<FacetField, readonly FacetBucket[]>>>;
80
80
  /**
81
- * The facet-field-name union of a search type — the keys a {@link SearchResult}’s
81
+ * The facet-field-name union of a search type – the keys a {@link SearchResult}’s
82
82
  * `facets` can hold. Narrow for a declaration captured as a literal (via
83
83
  * `defineSearchType` or `as const satisfies SearchType`); a plain
84
84
  * `: SearchType` annotation degrades to `string` (its field names are not
@@ -88,7 +88,7 @@ export type FacetFieldsOf<Type extends SearchType> = SearchType extends Type ? s
88
88
  readonly facetable: true;
89
89
  }>['name'];
90
90
  /**
91
- * The output-field-name union of a search type — the keys a {@link ResultDocument}
91
+ * The output-field-name union of a search type – the keys a {@link ResultDocument}
92
92
  * can hold. Like {@link FacetFieldsOf}, degrades to `string` for a widened
93
93
  * declaration.
94
94
  */
@@ -98,7 +98,7 @@ export type OutputFieldsOf<Type extends SearchType> = SearchType extends Type ?
98
98
  /**
99
99
  * One result row. `id` (the stable document key, an IRI) is kept *out* of
100
100
  * {@link ResultDocument}: it is always present and is the hit’s identity, a
101
- * different contract from the optional, typed logical field values — and it maps
101
+ * different contract from the optional, typed logical field values – and it maps
102
102
  * straight onto the GraphQL output’s guaranteed `id: String!`. The document
103
103
  * holds only the selectable fields.
104
104
  */
@@ -107,7 +107,7 @@ export interface SearchHit<OutputField extends string = string> {
107
107
  readonly document: ResultDocument<OutputField>;
108
108
  }
109
109
  /**
110
- * The logical result document at the query seam — engine- and RDF-neutral.
110
+ * The logical result document at the query seam – engine- and RDF-neutral.
111
111
  * Distinct from the flat, fanned-out projection `SearchDocument` that lives
112
112
  * index-side: this carries logical fields with language maps and references,
113
113
  * ready for a surface to shape. Keyed by output field name; `Partial` because a
@@ -2,29 +2,29 @@ import type { Quad } from '@rdfjs/types';
2
2
  /** A framed JSON-LD node (full-IRI keys); the engine-agnostic search IR. */
3
3
  export type FramedNode = Record<string, unknown>;
4
4
  /**
5
- * A one-pass index of a quad source: every subject’s (deduplicated) triples,
6
- * plus the root subjects of each requested type in appearance order. Built by
7
- * {@link buildSubjectIndex} from a single iteration of the source, so a caller
8
- * can pass a chained generator (`function* () { yield* a; yield* b; }`) rather
9
- * than materialize a merged array; a multi-type projection then frames every
10
- * type off this one index instead of re-scanning per type.
5
+ * A one-pass index of a quad source: every subject’s (deduplicated) triples.
6
+ * Built by {@link buildSubjectIndex} from a single iteration of the source, so a
7
+ * caller can pass a chained generator (`function* () { yield* a; yield* b; }`)
8
+ * rather than materialize a merged array, then frame any of its subjects off
9
+ * this one index.
11
10
  */
12
11
  export interface SubjectIndex {
13
12
  /** Each subject IRI → its own deduplicated triples, in first-seen order. */
14
13
  readonly bySubject: ReadonlyMap<string, readonly Quad[]>;
15
- /** Each requested root type IRI → its root subjects, in appearance order. */
16
- readonly rootsByType: ReadonlyMap<string, readonly string[]>;
17
14
  }
18
15
  /**
19
- * Iterate `quads` once, grouping each subject’s triples and collecting the root
20
- * subjects of every type in `rootTypes`. Duplicate triples are collapsed here
21
- * because some SPARQL engines (e.g. QLever) do not deduplicate CONSTRUCT
22
- * output. The source is consumed a single time, so it may be a one-shot
23
- * iterable (a generator chaining several sources); the whole subject index is
24
- * held, but never more than that plus one framed subgraph at a time
16
+ * Iterate `quads` once, grouping each subject’s triples. Duplicate triples are
17
+ * collapsed here because some SPARQL engines (e.g. QLever) do not deduplicate
18
+ * CONSTRUCT output. The source is consumed a single time, so it may be a
19
+ * one-shot iterable (a generator chaining several sources); the whole subject
20
+ * index is held, but never more than that plus one framed subgraph at a time
25
21
  * downstream.
22
+ *
23
+ * Roots are never discovered here – the caller ({@link projectRoots}, from the
24
+ * pipeline selector) already holds them and passes them to {@link frameSubjects}
25
+ * – so `quads` need carry no `rdf:type` triple.
26
26
  */
27
- export declare function buildSubjectIndex(quads: Iterable<Quad>, rootTypes: Iterable<string>): SubjectIndex;
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
30
  * JSON-LD node. Each root subject’s own triples plus the one-hop nodes it
@@ -33,19 +33,9 @@ export declare function buildSubjectIndex(quads: Iterable<Quad>, rootTypes: Iter
33
33
  * is held (whole-graph `jsonld.frame()` is ~O(N²)). The frame carries no
34
34
  * `@context`, so framed keys are full predicate IRIs.
35
35
  *
36
- * The roots are supplied explicitly rather than discovered from `rdf:type`: a
37
- * caller with the roots already in hand ({@link projectRoots}, from the pipeline
38
- * selector) passes them directly; a whole-graph caller ({@link frameByType},
39
- * {@link projectGraph}) passes {@link SubjectIndex.rootsByType}. A root absent
40
- * from the index simply frames nothing.
36
+ * The roots are supplied explicitly rather than discovered from `rdf:type`: the
37
+ * caller ({@link projectRoots}, from the pipeline selector) already holds them
38
+ * and passes them directly. A root absent from the index simply frames nothing.
41
39
  */
42
40
  export declare function frameSubjects(index: SubjectIndex, roots: readonly string[]): AsyncIterable<FramedNode>;
43
- /**
44
- * Frame CONSTRUCT quads into one JSON-LD node per subject of `rootType`, for a
45
- * single root type. Consumes `quads` once (see {@link buildSubjectIndex}), so
46
- * it accepts any `Iterable` – a materialized array or a chained generator. For
47
- * a multi-type schema, {@link buildSubjectIndex} + {@link frameSubjects} share
48
- * one index across types rather than re-scanning per type.
49
- */
50
- export declare function frameByType(quads: Iterable<Quad>, rootType: string): AsyncIterable<FramedNode>;
51
41
  //# 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;AAMzC,4EAA4E;AAC5E,MAAM,MAAM,UAAU,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;AAIjD;;;;;;;GAOG;AACH,MAAM,WAAW,YAAY;IAC3B,4EAA4E;IAC5E,QAAQ,CAAC,SAAS,EAAE,WAAW,CAAC,MAAM,EAAE,SAAS,IAAI,EAAE,CAAC,CAAC;IACzD,6EAA6E;IAC7E,QAAQ,CAAC,WAAW,EAAE,WAAW,CAAC,MAAM,EAAE,SAAS,MAAM,EAAE,CAAC,CAAC;CAC9D;AAED;;;;;;;;GAQG;AACH,wBAAgB,iBAAiB,CAC/B,KAAK,EAAE,QAAQ,CAAC,IAAI,CAAC,EACrB,SAAS,EAAE,QAAQ,CAAC,MAAM,CAAC,GAC1B,YAAY,CA8Bd;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAuB,aAAa,CAClC,KAAK,EAAE,YAAY,EACnB,KAAK,EAAE,SAAS,MAAM,EAAE,GACvB,aAAa,CAAC,UAAU,CAAC,CA4B3B;AAED;;;;;;GAMG;AACH,wBAAuB,WAAW,CAChC,KAAK,EAAE,QAAQ,CAAC,IAAI,CAAC,EACrB,QAAQ,EAAE,MAAM,GACf,aAAa,CAAC,UAAU,CAAC,CAG3B"}
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;;;;;;;;;;;GAWG;AACH,wBAAuB,aAAa,CAClC,KAAK,EAAE,YAAY,EACnB,KAAK,EAAE,SAAS,MAAM,EAAE,GACvB,aAAa,CAAC,UAAU,CAAC,CA6B3B"}
@@ -1,22 +1,19 @@
1
1
  import jsonld from 'jsonld';
2
- import { rdf } from '@tpluscode/rdf-ns-builders';
3
- const RDF_TYPE = rdf.type.value;
4
2
  const FRAME_OPTIONS = { omitGraph: false, embed: '@always' };
5
3
  /**
6
- * Iterate `quads` once, grouping each subject’s triples and collecting the root
7
- * subjects of every type in `rootTypes`. Duplicate triples are collapsed here
8
- * because some SPARQL engines (e.g. QLever) do not deduplicate CONSTRUCT
9
- * output. The source is consumed a single time, so it may be a one-shot
10
- * iterable (a generator chaining several sources); the whole subject index is
11
- * held, but never more than that plus one framed subgraph at a time
4
+ * Iterate `quads` once, grouping each subject’s triples. Duplicate triples are
5
+ * collapsed here because some SPARQL engines (e.g. QLever) do not deduplicate
6
+ * CONSTRUCT output. The source is consumed a single time, so it may be a
7
+ * one-shot iterable (a generator chaining several sources); the whole subject
8
+ * index is held, but never more than that plus one framed subgraph at a time
12
9
  * downstream.
10
+ *
11
+ * Roots are never discovered here – the caller ({@link projectRoots}, from the
12
+ * pipeline selector) already holds them and passes them to {@link frameSubjects}
13
+ * – so `quads` need carry no `rdf:type` triple.
13
14
  */
14
- export function buildSubjectIndex(quads, rootTypes) {
15
+ export function buildSubjectIndex(quads) {
15
16
  const bySubject = new Map();
16
- const rootsByType = new Map();
17
- for (const type of rootTypes) {
18
- rootsByType.set(type, []);
19
- }
20
17
  const seen = new Set();
21
18
  for (const quad of quads) {
22
19
  const key = `${quad.subject.value} ${quad.predicate.value} ${quad.object.value} ${quad.object.termType === 'Literal' ? quad.object.language || quad.object.datatype.value : ''}`;
@@ -32,16 +29,8 @@ export function buildSubjectIndex(quads, rootTypes) {
32
29
  else {
33
30
  owned.push(quad);
34
31
  }
35
- // `rootsByType` is keyed by exactly the requested root types, so the lookup
36
- // doubles as the membership test: a hit means this subject is a root.
37
- if (quad.predicate.value === RDF_TYPE) {
38
- const roots = rootsByType.get(quad.object.value);
39
- if (roots !== undefined) {
40
- roots.push(subject);
41
- }
42
- }
43
32
  }
44
- return { bySubject, rootsByType };
33
+ return { bySubject };
45
34
  }
46
35
  /**
47
36
  * Frame each of the given `roots` from a prebuilt {@link SubjectIndex} into one
@@ -51,11 +40,9 @@ export function buildSubjectIndex(quads, rootTypes) {
51
40
  * is held (whole-graph `jsonld.frame()` is ~O(N²)). The frame carries no
52
41
  * `@context`, so framed keys are full predicate IRIs.
53
42
  *
54
- * The roots are supplied explicitly rather than discovered from `rdf:type`: a
55
- * caller with the roots already in hand ({@link projectRoots}, from the pipeline
56
- * selector) passes them directly; a whole-graph caller ({@link frameByType},
57
- * {@link projectGraph}) passes {@link SubjectIndex.rootsByType}. A root absent
58
- * from the index simply frames nothing.
43
+ * The roots are supplied explicitly rather than discovered from `rdf:type`: the
44
+ * caller ({@link projectRoots}, from the pipeline selector) already holds them
45
+ * and passes them directly. A root absent from the index simply frames nothing.
59
46
  */
60
47
  export async function* frameSubjects(index, roots) {
61
48
  const { bySubject } = index;
@@ -67,11 +54,12 @@ export async function* frameSubjects(index, roots) {
67
54
  .flatMap((quad) => bySubject.get(quad.object.value) ?? []);
68
55
  const subgraph = [...owned, ...referenced];
69
56
  const expanded = await jsonld.fromRDF(subgraph);
70
- // Frame for THIS specific root subject by `@id`, not just by root type. A
71
- // one-hop reference can itself be of `rootType` (e.g. a terminology source
72
- // that is also a separately registered dataset), so framing by type alone
73
- // returns several root nodes and `[0]` could be the referenced one – which
74
- // would emit it twice and drop this subject entirely.
57
+ // Frame for THIS specific root subject by its `@id`. The subgraph embeds the
58
+ // root’s one-hop references, and such a referent can itself be one of the
59
+ // requested `roots` (e.g. a terminology source that is also a separately
60
+ // registered dataset). Framing by `{ '@id': rootIri }` pins the output to
61
+ // this subject, so the referent is framed once in its own turn rather than
62
+ // surfacing here as a second `@graph` node that `[0]` might pick instead.
75
63
  const framed = await jsonld.frame(expanded, { '@id': rootIri }, FRAME_OPTIONS);
76
64
  const node = framed['@graph']?.[0];
77
65
  if (node !== undefined) {
@@ -79,14 +67,3 @@ export async function* frameSubjects(index, roots) {
79
67
  }
80
68
  }
81
69
  }
82
- /**
83
- * Frame CONSTRUCT quads into one JSON-LD node per subject of `rootType`, for a
84
- * single root type. Consumes `quads` once (see {@link buildSubjectIndex}), so
85
- * it accepts any `Iterable` – a materialized array or a chained generator. For
86
- * a multi-type schema, {@link buildSubjectIndex} + {@link frameSubjects} share
87
- * one index across types rather than re-scanning per type.
88
- */
89
- export async function* frameByType(quads, rootType) {
90
- const index = buildSubjectIndex(quads, [rootType]);
91
- yield* frameSubjects(index, index.rootsByType.get(rootType) ?? []);
92
- }
package/dist/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
- export { projectGraph, projectRoots, irisOf, literalsOf, firstLiteralOf, } from './project.js';
2
- export type { SearchDocument, TypedSearchDocument } from './project.js';
1
+ export { projectRoots } from './project.js';
2
+ export type { SearchDocument } from './project.js';
3
3
  export { defineSearchType, searchSchema, validateSearchType, assertValidSearchType, } from './schema.js';
4
4
  export type { FieldKind, SearchField, SearchFieldBase, TextField, KeywordField, ReferenceField, NumericField, BooleanField, SearchType, SearchTypeIssue, SearchSchema, FacetRange, } from './schema.js';
5
5
  export type { SearchQuery, Filter, Sort } from './query.js';
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AASA,OAAO,EACL,YAAY,EACZ,YAAY,EACZ,MAAM,EACN,UAAU,EACV,cAAc,GACf,MAAM,cAAc,CAAC;AACtB,YAAY,EAAE,cAAc,EAAE,mBAAmB,EAAE,MAAM,cAAc,CAAC;AAKxE,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,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/index.js CHANGED
@@ -4,9 +4,9 @@
4
4
  // under `@lde/search/adapter`; the engine-port conformance suite under
5
5
  // `@lde/search/testing`.
6
6
  // Projection: RDF CONSTRUCT quads → flat search documents, driven by the
7
- // unified SearchField/SearchType model. The IR readers (irisOf, …) are here
8
- // because `derive` functions are written against them.
9
- export { projectGraph, projectRoots, irisOf, literalsOf, firstLiteralOf, } from './project.js';
7
+ // unified SearchField/SearchType model. A `derive` reads the projected document,
8
+ // never the graph, so the IR readers stay internal to projection.
9
+ export { projectRoots } from './project.js';
10
10
  // Unified field model: one declaration drives projection, engine collection
11
11
  // schema, query semantics and the GraphQL surface – a discriminated union by
12
12
  // `kind`, validated again at runtime when the schema is built.
package/dist/project.d.ts CHANGED
@@ -5,56 +5,31 @@ import { type SearchSchema, type SearchType } from './schema.js';
5
5
  export type SearchDocument = {
6
6
  id: string;
7
7
  } & Record<string, unknown>;
8
- /**
9
- * A projected document tagged with the {@link SearchType} it was projected
10
- * from – one element of {@link projectGraph}’s mixed, whole-schema stream. The
11
- * tag is what lets a multi-collection writer route each document to the
12
- * collection for its type without re-deriving the type from the document.
13
- */
14
- export interface TypedSearchDocument {
15
- readonly searchType: SearchType;
16
- readonly document: SearchDocument;
17
- }
18
8
  /**
19
9
  * Project one framed JSON-LD node into a flat search document: apply each field
20
10
  * of the type in declaration order. A field with a `derive` function computes
21
- * its value from the node and the document as populated so far (so a derived
22
- * field may read fields declared before it). The physical field names a field
23
- * fans out to come from {@link physicalFields}, the single source shared with
24
- * the engine collection definition and the query compiler.
11
+ * its value from the document as populated so far (so a derived field may read
12
+ * fields declared before it), never from the graph – `path` is the complete
13
+ * statement of what the projection reads. {@link isInternalField Internal
14
+ * fields} (those declaring no role) are populated so a later derive can read
15
+ * them, then pruned before the document is returned: they must reach neither a
16
+ * writer nor the collection definition. The physical field names a field fans
17
+ * out to come from {@link physicalFields}, the single source shared with the
18
+ * engine collection definition and the query compiler.
25
19
  */
26
20
  export declare function projectDocument(node: FramedNode, searchType: SearchType): SearchDocument;
27
- /**
28
- * Frame `quads` for every root type in the schema and project each node with its
29
- * type’s declaration – the multi-shape pipeline. Yields each document tagged
30
- * with its {@link SearchType} ({@link TypedSearchDocument}), so a downstream
31
- * multi-collection writer can route it to that type’s collection; a
32
- * single-collection consumer just reads `.document`. Streams one document at a
33
- * time so memory stays flat. The IR maps to a declaration by type, so adding a
34
- * shape is adding a `SearchType` to the schema (no engine change).
35
- *
36
- * Consumes `quads` once (a single scan builds the shared subject index that
37
- * every type frames off), so it accepts any `Iterable` – a materialized array or
38
- * a chained generator merging several sources (`function* () { yield* a; yield* b; }`)
39
- * with no intermediate copy at the projection peak.
40
- */
41
- export declare function projectGraph(quads: Iterable<Quad>, schema: SearchSchema): AsyncIterable<TypedSearchDocument>;
42
21
  /**
43
22
  * Project a single type over a known set of `roots` – the per-type, roots-given
44
- * projection. Unlike {@link projectGraph}, the roots are supplied by the caller
45
- * (the pipeline selector) rather than discovered from `rdf:type`, so `quads`
46
- * need carry no type triples and the projection frames each distinct subject
47
- * once. {@link assertTypeInSchema} guards that `searchType` belongs to `schema`
48
- * – the port’s own membership check – so no schema is ever forged to scope a
49
- * projection to one type. Yields a bare {@link SearchDocument}: tagging a
50
- * document with its type is a routing concern, owned by the pipeline glue, not
51
- * the projection.
23
+ * projection. The roots are supplied by the caller (the pipeline selector)
24
+ * rather than discovered from `rdf:type`, so `quads` need carry no type triples
25
+ * and the projection frames each distinct subject once. {@link assertTypeInSchema}
26
+ * guards that `searchType` belongs to `schema` – the port’s own membership check
27
+ * – so no schema is ever forged to scope a projection to one type. Yields a bare
28
+ * {@link SearchDocument}: pairing a document with its type is a routing concern,
29
+ * owned by the pipeline glue, not the projection.
52
30
  *
53
31
  * Consumes `quads` once, so it accepts any `Iterable` – a batch’s materialized
54
32
  * array or a chained generator merging several readers.
55
33
  */
56
34
  export declare function projectRoots(quads: Iterable<Quad>, roots: readonly string[], schema: SearchSchema, searchType: SearchType): AsyncIterable<SearchDocument>;
57
- export declare function literalsOf(node: FramedNode, path: string): string[];
58
- export declare function firstLiteralOf(node: FramedNode, path: string): string | undefined;
59
- export declare function irisOf(node: FramedNode, path: string): string[];
60
35
  //# sourceMappingURL=project.d.ts.map
@@ -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,EAQL,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;;;;;GAKG;AACH,MAAM,WAAW,mBAAmB;IAClC,QAAQ,CAAC,UAAU,EAAE,UAAU,CAAC;IAChC,QAAQ,CAAC,QAAQ,EAAE,cAAc,CAAC;CACnC;AAED;;;;;;;GAOG;AACH,wBAAgB,eAAe,CAC7B,IAAI,EAAE,UAAU,EAChB,UAAU,EAAE,UAAU,GACrB,cAAc,CAYhB;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAuB,YAAY,CACjC,KAAK,EAAE,QAAQ,CAAC,IAAI,CAAC,EACrB,MAAM,EAAE,YAAY,GACnB,aAAa,CAAC,mBAAmB,CAAC,CAYpC;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAuB,YAAY,CACjC,KAAK,EAAE,QAAQ,CAAC,IAAI,CAAC,EACrB,KAAK,EAAE,SAAS,MAAM,EAAE,EACxB,MAAM,EAAE,YAAY,EACpB,UAAU,EAAE,UAAU,GACrB,aAAa,CAAC,cAAc,CAAC,CAW/B;AAqJD,wBAAgB,UAAU,CAAC,IAAI,EAAE,UAAU,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,EAAE,CAInE;AAED,wBAAgB,cAAc,CAC5B,IAAI,EAAE,UAAU,EAChB,IAAI,EAAE,MAAM,GACX,MAAM,GAAG,SAAS,CAEpB;AAED,wBAAgB,MAAM,CAAC,IAAI,EAAE,UAAU,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,EAAE,CAI/D"}
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,EASL,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;;;;;;;;;;;GAWG;AACH,wBAAgB,eAAe,CAC7B,IAAI,EAAE,UAAU,EAChB,UAAU,EAAE,UAAU,GACrB,cAAc,CAoBhB;AAED;;;;;;;;;;;;GAYG;AACH,wBAAuB,YAAY,CACjC,KAAK,EAAE,QAAQ,CAAC,IAAI,CAAC,EACrB,KAAK,EAAE,SAAS,MAAM,EAAE,EACxB,MAAM,EAAE,YAAY,EACpB,UAAU,EAAE,UAAU,GACrB,aAAa,CAAC,cAAc,CAAC,CAU/B"}
package/dist/project.js CHANGED
@@ -1,13 +1,17 @@
1
1
  import { fold } from '@lde/text-normalization';
2
2
  import { buildSubjectIndex, frameSubjects, } from './frame-by-type.js';
3
- import { assertTypeInSchema, displayFieldName, isoToUnixSeconds, physicalFields, } from './schema.js';
3
+ import { assertTypeInSchema, displayFieldName, isInternalField, isoToUnixSeconds, physicalFields, } 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
7
- * its value from the node and the document as populated so far (so a derived
8
- * field may read fields declared before it). The physical field names a field
9
- * fans out to come from {@link physicalFields}, the single source shared with
10
- * the engine collection definition and the query compiler.
7
+ * its value from the document as populated so far (so a derived field may read
8
+ * fields declared before it), never from the graph – `path` is the complete
9
+ * statement of what the projection reads. {@link isInternalField Internal
10
+ * fields} (those declaring no role) are populated so a later derive can read
11
+ * them, then pruned before the document is returned: they must reach neither a
12
+ * writer nor the collection definition. The physical field names a field fans
13
+ * out to come from {@link physicalFields}, the single source shared with the
14
+ * engine collection definition and the query compiler.
11
15
  */
12
16
  export function projectDocument(node, searchType) {
13
17
  const id = node['@id'];
@@ -18,61 +22,43 @@ export function projectDocument(node, searchType) {
18
22
  for (const field of searchType.fields) {
19
23
  applyField(document, node, field);
20
24
  }
21
- return document;
22
- }
23
- /**
24
- * Frame `quads` for every root type in the schema and project each node with its
25
- * type’s declaration – the multi-shape pipeline. Yields each document tagged
26
- * with its {@link SearchType} ({@link TypedSearchDocument}), so a downstream
27
- * multi-collection writer can route it to that type’s collection; a
28
- * single-collection consumer just reads `.document`. Streams one document at a
29
- * time so memory stays flat. The IR maps to a declaration by type, so adding a
30
- * shape is adding a `SearchType` to the schema (no engine change).
31
- *
32
- * Consumes `quads` once (a single scan builds the shared subject index that
33
- * every type frames off), so it accepts any `Iterable` – a materialized array or
34
- * a chained generator merging several sources (`function* () { yield* a; yield* b; }`)
35
- * with no intermediate copy at the projection peak.
36
- */
37
- export async function* projectGraph(quads, schema) {
38
- const types = [...schema.values()];
39
- const index = buildSubjectIndex(quads, types.map((searchType) => searchType.class));
40
- for (const searchType of types) {
41
- const roots = index.rootsByType.get(searchType.class) ?? [];
42
- for await (const node of frameSubjects(index, roots)) {
43
- yield { searchType, document: projectDocument(node, searchType) };
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];
44
31
  }
45
32
  }
33
+ return document;
46
34
  }
47
35
  /**
48
36
  * Project a single type over a known set of `roots` – the per-type, roots-given
49
- * projection. Unlike {@link projectGraph}, the roots are supplied by the caller
50
- * (the pipeline selector) rather than discovered from `rdf:type`, so `quads`
51
- * need carry no type triples and the projection frames each distinct subject
52
- * once. {@link assertTypeInSchema} guards that `searchType` belongs to `schema`
53
- * – the port’s own membership check – so no schema is ever forged to scope a
54
- * projection to one type. Yields a bare {@link SearchDocument}: tagging a
55
- * document with its type is a routing concern, owned by the pipeline glue, not
56
- * the projection.
37
+ * projection. The roots are supplied by the caller (the pipeline selector)
38
+ * rather than discovered from `rdf:type`, so `quads` need carry no type triples
39
+ * and the projection frames each distinct subject once. {@link assertTypeInSchema}
40
+ * guards that `searchType` belongs to `schema` – the port’s own membership check
41
+ * – so no schema is ever forged to scope a projection to one type. Yields a bare
42
+ * {@link SearchDocument}: pairing a document with its type is a routing concern,
43
+ * owned by the pipeline glue, not the projection.
57
44
  *
58
45
  * Consumes `quads` once, so it accepts any `Iterable` – a batch’s materialized
59
46
  * array or a chained generator merging several readers.
60
47
  */
61
48
  export async function* projectRoots(quads, roots, schema, searchType) {
62
49
  assertTypeInSchema(schema, searchType);
63
- const index = buildSubjectIndex(quads, []);
50
+ const index = buildSubjectIndex(quads);
64
51
  // Distinct roots only. A selector may return an IRI more than once – a
65
52
  // non-`DISTINCT` `SELECT` over a one-to-many join yields the same subject per
66
53
  // matched row – and a repeated root would otherwise frame and emit a
67
- // duplicate document under the same `id`. (`projectGraph`’s roots come from
68
- // `rootsByType`, which is inherently unique, so it never needs this.)
54
+ // duplicate document under the same `id`.
69
55
  for await (const node of frameSubjects(index, [...new Set(roots)])) {
70
56
  yield projectDocument(node, searchType);
71
57
  }
72
58
  }
73
59
  function applyField(document, node, field) {
74
60
  if (field.derive !== undefined) {
75
- const value = field.derive(node, document);
61
+ const value = field.derive(document);
76
62
  if (value !== undefined) {
77
63
  document[field.name] = value;
78
64
  }
@@ -172,15 +158,15 @@ function langValuesOf(node, path) {
172
158
  .map(toLangValue)
173
159
  .filter((value) => value !== undefined);
174
160
  }
175
- export function literalsOf(node, path) {
161
+ function literalsOf(node, path) {
176
162
  return valuesOf(node, path)
177
163
  .map(literalString)
178
164
  .filter((value) => value !== undefined);
179
165
  }
180
- export function firstLiteralOf(node, path) {
166
+ function firstLiteralOf(node, path) {
181
167
  return literalsOf(node, path)[0];
182
168
  }
183
- export function irisOf(node, path) {
169
+ function irisOf(node, path) {
184
170
  return valuesOf(node, path)
185
171
  .map(iriString)
186
172
  .filter((value) => value !== undefined);
package/dist/schema.d.ts CHANGED
@@ -1,4 +1,3 @@
1
- import type { FramedNode } from './frame-by-type.js';
2
1
  import type { SearchDocument } from './project.js';
3
2
  /**
4
3
  * The engine-neutral kind of a queryable field. It drives every downstream
@@ -32,9 +31,11 @@ export declare function filterOperatorFor(kind: FieldKind): FilterOperator | und
32
31
  * Capability flags (`searchable`/`filterable`/`facetable`/`sortable`/`output`)
33
32
  * are independent opt-ins: a field exposes exactly the roles it declares. A
34
33
  * field with a {@link SearchFieldBase.derive `derive`} function instead of a
35
- * `path` is a **derived field** – computed from the framed node rather than
36
- * projected from the IR – yet it still carries full query/schema/output
37
- * behavior (e.g. `status`, the compatibility booleans).
34
+ * `path` is a **derived field** – computed from the document projected so far
35
+ * rather than read from the graph – yet it still carries full query/schema/output
36
+ * behavior (e.g. `status`, the compatibility booleans). A field declaring **no**
37
+ * role at all is an {@link isInternalField **internal field**}: projected so a
38
+ * later derive can read it, then pruned before the writer.
38
39
  *
39
40
  * The physical field names a declaration fans out to (per-locale search/sort
40
41
  * keys) follow one convention, owned by
@@ -73,15 +74,17 @@ export interface SearchFieldBase {
73
74
  readonly sortable?: boolean;
74
75
  /**
75
76
  * Compute this field’s value instead of projecting it from a `path` – a
76
- * status token, a compatibility boolean, a count over the framed node.
77
+ * status token, a compatibility boolean, a count over an earlier field.
77
78
  * Mutually exclusive with `path`. Runs in declaration order during
78
- * projection, receiving the framed node and the document as populated so
79
- * far, so a derived field may read fields declared before it (e.g. a
80
- * `statusRank` reading the derived `status`). Return `undefined` to leave
81
- * the field absent. The field still carries full query/schema/output
82
- * behaviour like any other.
79
+ * projection, receiving **only** the document as populated so far – never the
80
+ * graph – so a derived field reads fields declared before it (e.g. a
81
+ * `statusRank` reading the derived `status`, or a count reading an
82
+ * {@link isInternalField internal} field). Return `undefined` to leave the
83
+ * field absent. The field still carries full query/schema/output behaviour
84
+ * like any other. Reading only the document is what keeps `path` the complete
85
+ * statement of what the projection reads from the graph.
83
86
  */
84
- readonly derive?: (node: FramedNode, document: SearchDocument) => unknown;
87
+ readonly derive?: (document: SearchDocument) => unknown;
85
88
  }
86
89
  /** Full-text inclusion with a `query_by` weight (folded; per-locale for
87
90
  * localized text). Presence is what makes a field searchable. */
@@ -374,6 +377,19 @@ export declare function filterableFields(searchType: SearchType): readonly Searc
374
377
  export declare function sortableFields(searchType: SearchType): readonly SearchField[];
375
378
  /** Fields that appear in the API output type, in declaration order. */
376
379
  export declare function outputFields(searchType: SearchType): readonly SearchField[];
380
+ /**
381
+ * Whether a field declares **no** role – none of `output`, `searchable`,
382
+ * `filterable`, `facetable`, `sortable`. Such a field is an **internal field**:
383
+ * the projection populates it (so a later {@link SearchFieldBase.derive} can
384
+ * read it), then prunes it before the document reaches a writer, and the engine
385
+ * collection definition omits it entirely – not stored, not indexed, no RAM.
386
+ * Absence of a role declares that intent; there is no separate marker flag.
387
+ *
388
+ * The single predicate the projection and the collection definition share, so
389
+ * they cannot disagree on what is internal. See the Search context’s load-bearing
390
+ * line: *a field without a Role is an Internal Field.*
391
+ */
392
+ export declare function isInternalField(field: SearchField): boolean;
377
393
  /** Fields of kind `reference` (IRI-valued, label-resolved), in declaration order. */
378
394
  export declare function referenceFields(searchType: SearchType): readonly ReferenceField[];
379
395
  /** Look up a field by its logical name. */
@@ -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
@@ -317,6 +317,25 @@ export function sortableFields(searchType) {
317
317
  export function outputFields(searchType) {
318
318
  return searchType.fields.filter((field) => field.output === true);
319
319
  }
320
+ /**
321
+ * Whether a field declares **no** role – none of `output`, `searchable`,
322
+ * `filterable`, `facetable`, `sortable`. Such a field is an **internal field**:
323
+ * the projection populates it (so a later {@link SearchFieldBase.derive} can
324
+ * read it), then prunes it before the document reaches a writer, and the engine
325
+ * collection definition omits it entirely – not stored, not indexed, no RAM.
326
+ * Absence of a role declares that intent; there is no separate marker flag.
327
+ *
328
+ * The single predicate the projection and the collection definition share, so
329
+ * they cannot disagree on what is internal. See the Search context’s load-bearing
330
+ * line: *a field without a Role is an Internal Field.*
331
+ */
332
+ export function isInternalField(field) {
333
+ return (field.output !== true &&
334
+ field.searchable === undefined &&
335
+ field.filterable !== true &&
336
+ field.facetable !== true &&
337
+ field.sortable !== true);
338
+ }
320
339
  /** Fields of kind `reference` (IRI-valued, label-resolved), in declaration order. */
321
340
  export function referenceFields(searchType) {
322
341
  return searchType.fields.filter((field) => field.kind === 'reference');
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lde/search",
3
- "version": "0.8.1",
3
+ "version": "0.10.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",
@@ -39,12 +39,12 @@
39
39
  "dependencies": {
40
40
  "@lde/text-normalization": "^0.1.1",
41
41
  "@rdfjs/types": "^2.0.1",
42
- "@tpluscode/rdf-ns-builders": "^5.0.0",
43
42
  "inflection": "^3.0.2",
44
43
  "jsonld": "^9.0.0",
45
44
  "tslib": "^2.3.0"
46
45
  },
47
46
  "devDependencies": {
47
+ "@tpluscode/rdf-ns-builders": "^5.0.0",
48
48
  "@types/jsonld": "^1.5.15",
49
49
  "n3": "^2.1.1"
50
50
  },