@lde/search 0.12.1 → 0.12.3

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
@@ -6,395 +6,12 @@ a running search engine needs is derived from it: the document projection, the
6
6
  engine collection definition, the query semantics, and the API surface. All these
7
7
  are kept in sync automatically rather than handwritten per deployment.
8
8
 
9
- The core itself is **engine-, API- and domain-agnostic**: it bakes in no search
10
- engine, no API protocol, and no domain vocabulary. The engine- and API-specific
11
- halves are adapters that plug into the ports defined here:
9
+ ## Installation
12
10
 
13
- - **engine adapters** implement the `SearchEngine` port:
14
- [`@lde/search-typesense`](../search-typesense), with OpenSearch to follow;
15
- - **API surfaces** drive it, parsing client input into `search(SearchQuery)`:
16
- [`@lde/search-api-graphql`](../search-api-graphql), with a REST
17
- surface to follow.
18
-
19
- The library never names your domain: the same core drives a `Dataset`,
20
- `Person`, or `CreativeWork` search.
21
-
22
- It provides four things:
23
-
24
- - **unified field model** – `SearchField` / `SearchType` / `SearchSchema`:
25
- one declaration per field that drives all four consumers below, so they
26
- cannot drift;
27
- - **neutral query IR** – `SearchQuery` / `Filter` / `Sort` + filter
28
- semantics: every API surface compiles into it, every engine adapter compiles
29
- out of it, so the two cannot drift;
30
- - **engine port** – `SearchEngine` and the logical result types
31
- (`SearchResult` / `SearchHit` / `ResultDocument` / `Reference` / …);
32
- - **streaming projection** – `projectRoots`, RDF `CONSTRUCT` quads → flat
33
- search documents, one root type at a time.
34
-
35
- ```
36
- SearchSchema ─┬─► projection (projectRoots → flat documents) [here]
37
- ├─► engine adapter (collection definition + query compiler) e.g. @lde/search-typesense
38
- ├─► query semantics (SearchQuery, filter/sort/facet) [here]
39
- └─► API surface (GraphQL / REST) e.g. @lde/search-api-graphql
40
- ```
41
-
42
- At runtime, everything those consumers do is a **pure transformation between
43
- data shapes**, each one parameterised by the schema – three chains, meeting at
44
- the engine:
45
-
46
- ```
47
- indexing: RDF quads ──frame──► FramedNode ──project──► SearchDocument ──import──► engine
48
- querying: client input ──parse──► SearchQuery ──compile──► engine query
49
- results: engine response ──parse──► SearchResult ──shape──► API output
50
- ```
51
-
52
- Validation happens before the first arrow (SHACL over the RDF) and inside the
53
- last (the engine enforces its collection definition); between them every stage is
54
- a typed, deterministic function – easy to test, and swappable per deployment.
55
-
56
- ## Entry points
57
-
58
- Exports are stratified by audience:
59
-
60
- - **`@lde/search`** – the authoring surface: `defineSearchType`,
61
- `searchSchema`, `projectRoots`, validation, and every model/query/result type.
62
- - **`@lde/search/adapter`** – plumbing for engine adapters and API surfaces:
63
- `physicalFields`, the field selectors, `assertValidQuery`, the filter
64
- operators and storage codecs.
65
- - **`@lde/search/testing`** – `describeSearchEngineContract`, the executable
66
- port contract every engine adapter runs against a live instance of itself
67
- (vitest; optional peer).
68
-
69
- ## Terminology
70
-
71
- The model has three levels, with analogues in SHACL ([one possible source](#why-a-declarative-model))
72
- and GraphQL (one of the surfaces):
73
-
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)).
90
-
91
- `projectRoots` and the engine port each execute one `SearchType` at a time –
92
- projection over the roots the pipeline selector supplied for that type; the
93
- GraphQL surface consumes the whole `SearchSchema`.
94
-
95
- ### API conventions
96
-
97
- Two conventions hold across the whole family:
98
-
99
- - **Parameter order** – a function takes the value it operates on first and
100
- the declaration right after it: `projectDocument(node, type)`,
101
- `buildSearchParams(query, type)`,
102
- `createTypesenseSearchEngine(client, schema, options)`,
103
- `engine.search(type, query)`.
104
- - **Factory verbs** – the verb tells you what kind of thing comes back.
105
- `define*` captures a declaration as a literal (`defineSearchType`);
106
- `build*` is a pure data-to-data constructor (`buildCollectionDefinition`,
107
- `buildSearchParams`, `buildGraphQLSchema`); `create*` makes a stateful
108
- instance (`createTypesenseSearchEngine`). A bare noun (`searchSchema`)
109
- constructs the trivial container it names.
110
-
111
- ## Field model
112
-
113
- The mapping is data, not code. Each field declares its `kind`, the IR `path` to
114
- read (or a `derive` function for a **derived** field, computed from the document
115
- in declaration order – so it may read fields declared before it, never the
116
- graph), and the capabilities (**roles**) it opts into. `path` is therefore the
117
- complete statement of what the projection reads from the graph. A field that
118
- declares **no** role is an **internal field**: projected so a later `derive` can
119
- read it, then pruned before the writer and absent from the collection definition
120
- – not stored, not indexed, no RAM. The physical field names a declaration fans
121
- out to (per-locale search/sort keys) come from `physicalFields`, the single
122
- convention projection, the collection definition and the query compiler all
123
- share.
124
-
125
- ```ts
126
- import { defineSearchType, projectRoots, searchSchema } from '@lde/search';
127
-
128
- const DATASET = defineSearchType({
129
- name: 'Dataset', // logical API name: names the GraphQL type, a REST path, …
130
- class: 'http://www.w3.org/ns/dcat#Dataset',
131
- fields: [
132
- // → title_<lang> (display, every present language), title_search_nl/_en, title_sort_nl/_en
133
- {
134
- name: 'title',
135
- path: 'http://purl.org/dc/terms/title',
136
- kind: 'text',
137
- locales: ['nl', 'en'],
138
- output: true,
139
- searchable: { weight: 5 },
140
- sortable: true,
141
- },
142
- // → publisher (IRI facet, resolved to a labelled reference at the surface)
143
- {
144
- name: 'publisher',
145
- path: 'http://purl.org/dc/terms/publisher',
146
- kind: 'reference',
147
- facetable: true,
148
- output: true,
149
- ref: { typeName: 'Organization', strategy: 'labelOnly' },
150
- },
151
- // → size (int)
152
- { name: 'size', path: 'urn:dr:size', kind: 'integer', sortable: true },
153
- // internal field (no role): projected as a reading device for the derive
154
- // below, then pruned before the writer – absent from the collection too
155
- { name: 'classes', path: 'urn:dr:class', kind: 'reference' },
156
- // derived field (no path): computed from the document in declaration order,
157
- // never from the graph – so `path` stays the whole statement of what is read
158
- {
159
- name: 'classCount',
160
- kind: 'integer',
161
- sortable: true,
162
- derive: (document) =>
163
- (document.classes as string[] | undefined)?.length ?? 0,
164
- },
165
- ],
166
- });
167
-
168
- const schema = searchSchema(DATASET);
169
- for await (const document of projectRoots(quads, roots, schema, DATASET)) {
170
- // one flat search document per given root subject, streamed. The caller (the
171
- // pipeline selector) supplies `roots`; pairing a document with its type for a
172
- // multi-collection writer is the pipeline glue’s job (see
173
- // `@lde/search-pipeline`), not the projection’s.
174
- }
11
+ ```sh
12
+ npm install @lde/search
175
13
  ```
176
14
 
177
- `defineSearchType` captures the declaration as a literal (what
178
- `as const satisfies SearchType` would do manually, with nothing to remember),
179
- so typed facet/output keys can be derived from it – see
180
- [Typed results](#typed-results) and `@lde/search-api-graphql`.
181
-
182
- **Kinds** (`FieldKind`): `text`, `keyword`, `integer`, `number`, `boolean`,
183
- `date`, `reference`. The Typesense/engine vocabulary and the GraphQL types are
184
- _derived_ from the kind by the adapter and the surface – never declared here.
185
-
186
- `SearchField` is a **discriminated union by `kind`** (`TextField`,
187
- `KeywordField`, `ReferenceField`, `NumericField`, `BooleanField`): each kind
188
- declares exactly the properties it can honour – `locales` on text, `ref` on
189
- references, `facetRanges` on numerics – so an illegal declaration fails to
190
- compile. Text is **always multilingual in shape**: `locales` lists the
191
- language tags to **index** (search/sort), and the reserved **`und`** locale
192
- (JSON-LD `@none`, RDF `und`) buckets untagged literals – a monolingual or
193
- untagged corpus declares `locales: ['und']`, mixed data `['nl', 'und']`.
194
- Display, by contrast, keeps every language present, not only the listed ones
195
- (see [Locales](#locales)). Declaring a real language is recommended (it drives
196
- per-locale stemming); `und` is folded but unstemmed unless `defaultLocale` opts
197
- in, is never demoted in search weighting, and adding a language later is
198
- additive – the API output shape never changes. Use `keyword` for exact-match
199
- tokens, never for prose.
200
-
201
- **Declarations are also validated at runtime** (for declarations built
202
- outside TypeScript – a SHACL generator, plain JS): `searchSchema()`
203
- rejects a structurally invalid declaration (duplicate field names, an `output`
204
- reference without `ref`, `text` without locales, `locales` on a non-text kind,
205
- `facetRanges` on a non-numeric kind, `searchable`/`transform` on a kind whose
206
- projection cannot honour it, `filterable`/`facetable` on `text`, two types
207
- sharing a `class` IRI or `name`) – the declaration-time counterpart of the
208
- port’s always-on query validation, so a bad schema fails at startup rather
209
- than per document at index time. `validateSearchType` /
210
- `assertValidSearchType` are exported for validating a single declaration
211
- directly.
212
-
213
- | kind | `where` | facet | sort | output |
214
- | -------------------- | -------------------- | ----- | ---------------- | ------------------------------- |
215
- | `text` | – (feeds free text) | – | yes (per-locale) | best-first language list |
216
- | `keyword` | `in` (membership) | yes | – | string / `string[]` |
217
- | `reference` | `in` (membership) | yes | – | labelled reference (id + label) |
218
- | `integer` / `number` | `range { min, max }` | yes | yes | number |
219
- | `date` | `range` (inclusive) | yes | yes | ISO 8601 string (surface) |
220
- | `boolean` | `is` | yes | – | boolean (absent = false) |
221
-
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.
245
-
246
- A reference resolves its label from a **label source**: `labelSource` names
247
- the `SearchType` whose collection holds the referenced entities. The named
248
- type must declare an `output`, `searchable` text field called `label` –
249
- `searchSchema` validates this schema-wide, so a dangling or unsuitable label
250
- source fails at startup. A reference without a `labelSource` stays id-only.
251
-
252
- ## Projection
253
-
254
- `projectRoots` projects **one root type** over the roots the caller supplies –
255
- the pipeline selector already holds them, so nothing is discovered from
256
- `rdf:type` and a `CONSTRUCT` need emit no type triple. It is fully streaming:
257
- each root’s subgraph is framed one at a time and its document yielded as
258
- produced, so beyond a subject index memory stays flat at scale (framing the
259
- whole graph at once is roughly O(N²)). Duplicate triples are collapsed first,
260
- because some SPARQL engines (e.g. QLever) do not deduplicate `CONSTRUCT` output.
261
- Every predicate a value comes from is read through a field’s `path`; a `derive`
262
- computes only from the document projected so far, so `path` is the whole
263
- statement of what the projection reads. `assertTypeInSchema` guards that
264
- the passed `SearchType` is a member of the schema – the port’s own membership
265
- check – so no schema is ever forged to scope a projection to one type.
266
-
267
- **Blank-node roots are not indexable**: a blank node has no stable document
268
- key, so framing skips a blank-node root (and any root absent from the index)
269
- rather than crash. Blank-node subjects still embed fine when _referenced_ from
270
- a root; they just cannot _be_ one – select roots accordingly
271
- (`selectByClass` in `@lde/search-pipeline` already excludes them).
272
-
273
- `projectRoots` yields a **bare** `SearchDocument`. Pairing each document with the
274
- `SearchType` it belongs to, so the write side can fan a mixed stream out to
275
- per-type collections, is a routing concern owned by the pipeline glue – see
276
- `@lde/search-pipeline`’s `searchStages` and multi-collection writer – not the
277
- projection. One stage per root type keeps `@lde/search` pipeline-free.
278
-
279
- `projectRoots` **consumes the quads once** – a single scan builds the subject
280
- index the roots frame off – and so accepts any `Iterable<Quad>`, not just a
281
- materialized array. A caller merging several readers can pass a chained
282
- generator instead of building a third full array at the projection peak:
283
-
284
- ```ts
285
- projectRoots(
286
- (function* () {
287
- yield* registerQuads;
288
- yield* dkgQuads;
289
- })(),
290
- roots,
291
- schema,
292
- DATASET,
293
- );
294
- ```
295
-
296
- ## Locales
297
-
298
- `locales` declares the languages a `text` field wants **indexed** (`und` =
299
- untagged literals). It drives the two per-locale, in-memory fanouts –
300
- `searchable` and `sortable` – but **not** display, which preserves every
301
- language present:
302
-
303
- - `output` → `title_<lang>` for **every** language the data carries (`title_nl`,
304
- `title_en`, `title_fr`, `title_und`, …), accents preserved. Display is stored
305
- `index: false` (on disk, off RAM), so extra languages cost nothing – a value
306
- in a language outside `locales`, or an untagged one, still renders rather than
307
- collapsing to a bare IRI. One un-indexed regex field (`${name}_<lang>`, see
308
- `displayFieldPattern`) captures them all;
309
- - `searchable` → `title_search_nl`/`title_search_en` (folded; one field per
310
- locale lets a query `query_by` them and rank the user’s language higher, and
311
- lets a language that needs a dedicated tokenizer set its own stemming `locale`
312
- in the engine schema);
313
- - `sortable` → `title_sort_nl`/`title_sort_en` (folded, so a locale-switching UI
314
- sorts on the active language).
315
-
316
- A field with `searchable` but no `output` is **search-only** – folded and stemmed
317
- for retrieval but never rendered (e.g. a creator searched here but shown via a
318
- separate label). **Only listed locales are indexed** (searched and sorted); a
319
- literal whose language tag is not in `locales` is still **displayed** but not
320
- matched or sorted on. Display fields are **omitted, never empty**, when a document
321
- lacks that language, and the per-locale search/sort fields likewise, so declare
322
- them optional in the engine schema and sort with `missing_values: last`. A
323
- deployment that wants to bound the displayed languages narrows them upstream
324
- (e.g. selecting a language subset in its CONSTRUCT query), since preservation is
325
- the default.
326
-
327
- Folding the search fields is what lets diacritic-insensitive matching and
328
- stemming coexist. A search engine on its **default** locale typically folds case
329
- and diacritics for you; enabling a language’s **stemming** switches it to ICU
330
- tokenization, which **preserves** diacritics – at which point `fold()` (from
331
- [`@lde/text-normalization`](../text-normalization)) is what keeps matching
332
- diacritic-insensitive. Stemming is rules-based and can mangle proper nouns (the
333
- Dutch stemmer reduces the city `Bergen` to `berg`), so enable it on free-text
334
- fields and keep proper-noun facets on a separate, unstemmed field.
335
-
336
- ## Querying
337
-
338
- The search fields are stored already case- and diacritic-folded, so **the query
339
- must be folded the same way** with the same `fold()` before it reaches the engine,
340
- or index and query normalize differently and matches silently miss. This contract
341
- holds for **any** consumer, including an API built on this package – which is why
342
- engine adapters and surfaces compile through the shared `SearchQuery` IR and the
343
- `physicalFields` convention rather than re-deriving field names.
344
-
345
- Queries are **always validated**: the port contract requires every engine
346
- adapter to reject a structurally invalid `SearchQuery` (`assertValidQuery`) –
347
- unknown or non-`filterable` fields in `where`, an operator not matching the
348
- field’s kind, non-`facetable` facet requests – no matter which surface or
349
- policy produced it. A typed surface like GraphQL makes most of these
350
- unrepresentable; the port enforces them for everyone else (deployment
351
- `queryDefaults`, in-process callers, weaker-typed surfaces).
352
-
353
- ## Typed results
354
-
355
- An engine is **bound to the whole `SearchSchema` at construction** – like
356
- every other schema consumer (`projectRoots(quads, roots, schema, type)`,
357
- `buildGraphQLSchema(schema)`): the adapter factory takes the deployment’s
358
- declaration, so a query can never meet the wrong index, and deployment-level
359
- concerns (the label cache, cross-type search, facet batching) have one home.
360
- Where each type physically lives is the **adapter’s** to decide – it derives a
361
- collection/index name from the type, by its own engine’s naming conventions,
362
- and a deployment only overrides that where it must. A search names its type per
363
- call. Because `searchSchema()` captures the declared types as a literal
364
- tuple, `search()` accepts **only the deployment’s own types** (a foreign type
365
- is a compile error) and returns facet/document keys typed by the type passed
366
- – no caller-side generics:
367
-
368
- ```ts
369
- // No `collections`: each type reads the collection the adapter names it,
370
- // which is the one its writer builds. Pass `collections` only to override.
371
- const engine = createTypesenseSearchEngine(client, schema);
372
-
373
- const result = await engine.search(DATASET, query);
374
- result.facets.publisher; // typed: only DATASET’s facetable fields are keys
375
- result.facets.publsher; // compile error (typo)
376
- result.hits[0].document.title; // typed: only DATASET’s output fields are keys
377
- await engine.search(OTHER_TYPE, query); // compile error: not in this schema
378
- ```
379
-
380
- `searchFacets(type, queries)` is the port’s **batch entry point**: several
381
- facet-only queries – e.g. a faceted listing’s skip-own-filter variants –
382
- answered in one engine round-trip (Typesense: a single `multi_search`), one
383
- outcome per query, positionally aligned – its facet map, or an in-place error,
384
- so one failed query never discards its siblings’ facets. The same schema
385
- binding, per-query validation and typed facet keys apply to every query in
386
- the batch.
387
-
388
- This only works when the types were declared with `defineSearchType` (or
389
- captured `as const satisfies SearchType`) and composed with `searchSchema()`;
390
- a plain `: SearchSchema` annotation widens gracefully to string keys.
391
- `FacetFieldsOf`/`OutputFieldsOf` are exported for annotating your own
392
- signatures, and `engine.schema` exposes the bound declaration for routing.
393
-
394
- ## Why a declarative model
15
+ ## Documentation
395
16
 
396
- The vocabulary mirrors SHACL on purpose: `path` is `sh:path`, `array` is
397
- `sh:maxCount`, `required` is `sh:minCount`, `locales` is `sh:languageIn`, `ref`
398
- is `sh:class`/`sh:node`. So the same core that runs a hand-written `SearchSchema`
399
- today will run a **SHACL-generated** one tomorrow – the model, the ports and the
400
- IR stay; only schema-authoring gets automated.
17
+ See the [full documentation](https://ldelements.org/reference/search).
@@ -0,0 +1,24 @@
1
+ import { type SearchSchema } from './schema.js';
2
+ /** What {@link loadSchemaModule} returns: the validated schema plus the raw
3
+ * module exports, so each consumer validates its own optional exports (the
4
+ * served API reads `schemaOptions`/`engineOptions`; the indexer reads none). */
5
+ export interface LoadedSchemaModule {
6
+ /** The validated schema built from the module’s default export. */
7
+ readonly schema: SearchSchema;
8
+ /** Every export of the module, for consumer-specific optional exports. */
9
+ readonly moduleExports: Record<string, unknown>;
10
+ }
11
+ /**
12
+ * Load and validate a mounted schema-declaration module: an ES module whose
13
+ * default export is a non-empty array of {@link SearchType} declarations –
14
+ * **plain data with optional functions** (`derive`, `transform`), because a
15
+ * mounted file cannot resolve bare imports like `@lde/search`. The one schema
16
+ * source both the indexer image and the served-API image mount, so the write
17
+ * and the read side cannot disagree about the schema.
18
+ *
19
+ * Throws with the module path in the message for every failure mode – an
20
+ * unreadable file, a wrong export shape, an invalid declaration – so a bad
21
+ * mount fails the boot with a diagnosis, never the first query.
22
+ */
23
+ export declare function loadSchemaModule(modulePath: string): Promise<LoadedSchemaModule>;
24
+ //# sourceMappingURL=module.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"module.d.ts","sourceRoot":"","sources":["../src/module.ts"],"names":[],"mappings":"AAIA,OAAO,EAAgB,KAAK,YAAY,EAAmB,MAAM,aAAa,CAAC;AAE/E;;iFAEiF;AACjF,MAAM,WAAW,kBAAkB;IACjC,mEAAmE;IACnE,QAAQ,CAAC,MAAM,EAAE,YAAY,CAAC;IAC9B,0EAA0E;IAC1E,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACjD;AAED;;;;;;;;;;;GAWG;AACH,wBAAsB,gBAAgB,CACpC,UAAU,EAAE,MAAM,GACjB,OAAO,CAAC,kBAAkB,CAAC,CA8B7B"}
package/dist/module.js ADDED
@@ -0,0 +1,42 @@
1
+ // The `@lde/search/module` entry point: load a mounted schema-declaration
2
+ // module. Node-only (it touches the filesystem), so it lives outside the main
3
+ // entry point, which stays runtime-agnostic.
4
+ import { pathToFileURL } from 'node:url';
5
+ import { searchSchema } from './schema.js';
6
+ /**
7
+ * Load and validate a mounted schema-declaration module: an ES module whose
8
+ * default export is a non-empty array of {@link SearchType} declarations –
9
+ * **plain data with optional functions** (`derive`, `transform`), because a
10
+ * mounted file cannot resolve bare imports like `@lde/search`. The one schema
11
+ * source both the indexer image and the served-API image mount, so the write
12
+ * and the read side cannot disagree about the schema.
13
+ *
14
+ * Throws with the module path in the message for every failure mode – an
15
+ * unreadable file, a wrong export shape, an invalid declaration – so a bad
16
+ * mount fails the boot with a diagnosis, never the first query.
17
+ */
18
+ export async function loadSchemaModule(modulePath) {
19
+ let moduleExports;
20
+ try {
21
+ moduleExports = (await import(pathToFileURL(modulePath).href));
22
+ }
23
+ catch (cause) {
24
+ throw new Error(`Cannot load schema module “${modulePath}”: ${messageOf(cause)}`, { cause });
25
+ }
26
+ const declarations = moduleExports['default'];
27
+ if (!Array.isArray(declarations) || declarations.length === 0) {
28
+ throw new Error(`Schema module “${modulePath}” must default-export a non-empty array of search type declarations.`);
29
+ }
30
+ try {
31
+ return {
32
+ schema: searchSchema(...declarations),
33
+ moduleExports,
34
+ };
35
+ }
36
+ catch (cause) {
37
+ throw new Error(`Schema module “${modulePath}” declares an invalid schema: ${messageOf(cause)}`, { cause });
38
+ }
39
+ }
40
+ function messageOf(cause) {
41
+ return cause instanceof Error ? cause.message : String(cause);
42
+ }
package/dist/schema.d.ts CHANGED
@@ -144,9 +144,13 @@ export interface ReferenceField extends SearchFieldBase, Searchable {
144
144
  /** Logical API type name of the referenced entity (PascalCase, e.g.
145
145
  * `Organization`) – names the reference’s type in the API surfaces, the
146
146
  * way {@link SearchType.name} names a root type; fields sharing it share
147
- * one emitted type. A name, not a key: it need not correspond to any
148
- * indexed root type (and until cross-collection references exist, it must
149
- * not collide with one). */
147
+ * one emitted type. For a `labelOnly`/`idOnly` reference it is a name,
148
+ * not a key: it need not correspond to any indexed root type, and it may
149
+ * name one (`creator` → `Person`, with the labels resolved from that
150
+ * root’s collection via `labelSource`) – an API surface then serves the
151
+ * reference under a derived name (GraphQL: `‹Name›Reference`) to keep
152
+ * type names unique. An `inline` reference’s typeName instead resolves to
153
+ * a declared {@link ReferenceType}, so it can never name a root type. */
150
154
  readonly typeName: string;
151
155
  /** How much of the referenced entity the reference carries. `labelOnly`
152
156
  * (id + display label, resolved from a label source) and `inline` (the
@@ -337,7 +341,7 @@ export declare function labelFieldOf(searchType: SearchType): TextField | undefi
337
341
  */
338
342
  export interface SearchTypeIssue {
339
343
  readonly field: string;
340
- readonly reason: 'duplicate-field-name' | 'invalid-field-name' | 'invalid-locale' | 'missing-ref' | 'ref-not-allowed' | 'text-requires-locales' | 'locales-not-allowed' | 'facet-ranges-not-allowed' | 'searchable-not-allowed' | 'transform-not-allowed' | 'derive-with-path' | 'text-not-filterable' | 'text-not-facetable';
344
+ readonly reason: 'duplicate-field-name' | 'invalid-field-name' | 'unknown-kind' | 'invalid-locale' | 'missing-ref' | 'ref-not-allowed' | 'text-requires-locales' | 'locales-not-allowed' | 'facet-ranges-not-allowed' | 'searchable-not-allowed' | 'transform-not-allowed' | 'derive-with-path' | 'text-not-filterable' | 'text-not-facetable';
341
345
  }
342
346
  /**
343
347
  * Structurally validate one {@link SearchType} declaration – the
@@ -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
@@ -277,6 +277,12 @@ export function validateSearchType(searchType) {
277
277
  if (!FIELD_NAME_PATTERN.test(field.name)) {
278
278
  issue('invalid-field-name');
279
279
  }
280
+ // Every kind-dependent rule below would silently pass for a kind outside
281
+ // the union, so a typo’d kind in a plain-JS declaration must fail here.
282
+ if (!Object.hasOwn(OPERATOR_BY_KIND, field.kind)) {
283
+ issue('unknown-kind');
284
+ continue;
285
+ }
280
286
  if (field.kind === 'reference') {
281
287
  if (field.output === true && field.ref === undefined) {
282
288
  issue('missing-ref');
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lde/search",
3
- "version": "0.12.1",
3
+ "version": "0.12.3",
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",
@@ -16,6 +16,12 @@
16
16
  "development": "./src/index.ts",
17
17
  "default": "./dist/index.js"
18
18
  },
19
+ "./module": {
20
+ "types": "./dist/module.d.ts",
21
+ "import": "./dist/module.js",
22
+ "development": "./src/module.ts",
23
+ "default": "./dist/module.js"
24
+ },
19
25
  "./adapter": {
20
26
  "types": "./dist/adapter.d.ts",
21
27
  "import": "./dist/adapter.js",
@@ -37,7 +43,7 @@
37
43
  "!**/*.tsbuildinfo"
38
44
  ],
39
45
  "dependencies": {
40
- "@lde/text-normalization": "^0.1.1",
46
+ "@lde/text-normalization": "^0.1.2",
41
47
  "@rdfjs/types": "^2.0.1",
42
48
  "inflection": "^3.0.2",
43
49
  "jsonld": "^9.0.0",