@lde/search 0.23.0 → 0.23.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/adapter.d.ts CHANGED
@@ -6,7 +6,7 @@
6
6
  * deployment author declaring a schema and running searches needs none of
7
7
  * this; the main entry point carries that (small) authoring surface.
8
8
  */
9
- export { assertTypeInSchema, physicalFields, irAlias, displayFieldName, displayFieldPattern, displayLangOf, searchableFields, facetableFields, filterableFields, sortableFields, outputFields, isInternalField, isInlineReference, nestedReferenceType, nestedFieldName, referenceFields, referenceTypeNamed, inlineFramingDepth, fieldNamed, datasetField, ID_FIELD, AND_KEY, OR_KEY, labelFieldOf, labelFieldNameOf, labelSourceNameOf, DEFAULT_LABEL_FIELD, isRangeFacet, isAbsoluteIri, isoToUnixSeconds, unixSecondsToIso, } from './schema.js';
9
+ export { assertTypeInSchema, physicalFields, irAlias, displayFieldName, displayFieldPattern, displayLangOf, searchableFields, facetableFields, filterableFields, sortableFields, outputFields, isInternalField, isInlineReference, nestedReferenceType, nestedFieldName, referenceFields, referenceTypeNamed, rootTypeNamed, inlineFramingDepth, fieldNamed, datasetField, ID_FIELD, AND_KEY, OR_KEY, labelFieldOf, labelFieldNameOf, labelSourceNameOf, documentKeyOf, DEFAULT_LABEL_FIELD, isRangeFacet, isAbsoluteIri, isoToUnixSeconds, unixSecondsToIso, } from './schema.js';
10
10
  export type { PhysicalFields } from './schema.js';
11
11
  export { physicalNameTokens } from './physical-name.js';
12
12
  export { joinGraph, MAX_JOIN_DEPTH } from './join-graph.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,OAAO,EACP,gBAAgB,EAChB,mBAAmB,EACnB,aAAa,EACb,gBAAgB,EAChB,eAAe,EACf,gBAAgB,EAChB,cAAc,EACd,YAAY,EACZ,eAAe,EACf,iBAAiB,EACjB,mBAAmB,EACnB,eAAe,EACf,eAAe,EACf,kBAAkB,EAClB,kBAAkB,EAClB,UAAU,EACV,YAAY,EACZ,QAAQ,EACR,OAAO,EACP,MAAM,EACN,YAAY,EACZ,gBAAgB,EAChB,iBAAiB,EACjB,mBAAmB,EACnB,YAAY,EACZ,aAAa,EACb,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,EAAE,SAAS,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AAC5D,YAAY,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AACjD,OAAO,EACL,iBAAiB,EACjB,cAAc,EACd,QAAQ,EACR,eAAe,EACf,aAAa,EACb,gBAAgB,EAChB,aAAa,GACd,MAAM,YAAY,CAAC;AACpB,YAAY,EAAE,cAAc,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC"}
1
+ {"version":3,"file":"adapter.d.ts","sourceRoot":"","sources":["../src/adapter.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,OAAO,EACL,kBAAkB,EAClB,cAAc,EACd,OAAO,EACP,gBAAgB,EAChB,mBAAmB,EACnB,aAAa,EACb,gBAAgB,EAChB,eAAe,EACf,gBAAgB,EAChB,cAAc,EACd,YAAY,EACZ,eAAe,EACf,iBAAiB,EACjB,mBAAmB,EACnB,eAAe,EACf,eAAe,EACf,kBAAkB,EAClB,aAAa,EACb,kBAAkB,EAClB,UAAU,EACV,YAAY,EACZ,QAAQ,EACR,OAAO,EACP,MAAM,EACN,YAAY,EACZ,gBAAgB,EAChB,iBAAiB,EACjB,aAAa,EACb,mBAAmB,EACnB,YAAY,EACZ,aAAa,EACb,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,EAAE,SAAS,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AAC5D,YAAY,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AACjD,OAAO,EACL,iBAAiB,EACjB,cAAc,EACd,QAAQ,EACR,eAAe,EACf,aAAa,EACb,gBAAgB,EAChB,aAAa,GACd,MAAM,YAAY,CAAC;AACpB,YAAY,EAAE,cAAc,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC"}
package/dist/adapter.js CHANGED
@@ -6,7 +6,7 @@
6
6
  * deployment author declaring a schema and running searches needs none of
7
7
  * this; the main entry point carries that (small) authoring surface.
8
8
  */
9
- export { assertTypeInSchema, physicalFields, irAlias, displayFieldName, displayFieldPattern, displayLangOf, searchableFields, facetableFields, filterableFields, sortableFields, outputFields, isInternalField, isInlineReference, nestedReferenceType, nestedFieldName, referenceFields, referenceTypeNamed, inlineFramingDepth, fieldNamed, datasetField, ID_FIELD, AND_KEY, OR_KEY, labelFieldOf, labelFieldNameOf, labelSourceNameOf, DEFAULT_LABEL_FIELD, isRangeFacet, isAbsoluteIri, isoToUnixSeconds, unixSecondsToIso, } from './schema.js';
9
+ export { assertTypeInSchema, physicalFields, irAlias, displayFieldName, displayFieldPattern, displayLangOf, searchableFields, facetableFields, filterableFields, sortableFields, outputFields, isInternalField, isInlineReference, nestedReferenceType, nestedFieldName, referenceFields, referenceTypeNamed, rootTypeNamed, inlineFramingDepth, fieldNamed, datasetField, ID_FIELD, AND_KEY, OR_KEY, labelFieldOf, labelFieldNameOf, labelSourceNameOf, documentKeyOf, DEFAULT_LABEL_FIELD, isRangeFacet, isAbsoluteIri, isoToUnixSeconds, unixSecondsToIso, } from './schema.js';
10
10
  export { physicalNameTokens } from './physical-name.js';
11
11
  export { joinGraph, MAX_JOIN_DEPTH } from './join-graph.js';
12
12
  export { filterOperatorFor, filterOperator, filterOn, isUnsatisfiable, validateQuery, assertValidQuery, pageForOffset, } from './query.js';
package/dist/index.d.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  export { projectRoots } from './project.js';
2
2
  export type { ProjectedNode, ProjectionContext, SearchDocument, } from './project.js';
3
3
  export { defineSearchType, searchSchema, validateSearchType, assertValidSearchType, ID_FIELD, AND_KEY, OR_KEY, isoToUnixSeconds, unixSecondsToIso, } from './schema.js';
4
- export type { FieldKind, SearchField, SearchFieldBase, TextField, KeywordField, ReferenceField, ReferenceStrategy, NumericField, BooleanField, SearchType, SearchTypeBase, RootType, ReferenceType, RootTypeOf, SearchTypeIssue, SearchSchema, FacetRange, ProjectionValue, } from './schema.js';
4
+ export type { FieldKind, SearchField, SearchFieldBase, TextField, KeywordField, ReferenceField, ReferenceStrategy, NumericField, BooleanField, SearchType, SearchTypeBase, RootType, ReferenceType, RootTypeOf, SearchTypeIssue, KeyField, FacetKeys, SearchSchema, FacetRange, ProjectionValue, } from './schema.js';
5
5
  export { joinGraph, MAX_JOIN_DEPTH } from './join-graph.js';
6
6
  export type { JoinGraph } from './join-graph.js';
7
7
  export { filterOn } from './query.js';
@@ -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,EACV,aAAa,EACb,iBAAiB,EACjB,cAAc,GACf,MAAM,cAAc,CAAC;AAKtB,OAAO,EACL,gBAAgB,EAChB,YAAY,EACZ,kBAAkB,EAClB,qBAAqB,EACrB,QAAQ,EACR,OAAO,EACP,MAAM,EAMN,gBAAgB,EAChB,gBAAgB,GACjB,MAAM,aAAa,CAAC;AACrB,YAAY,EACV,SAAS,EACT,WAAW,EACX,eAAe,EACf,SAAS,EACT,YAAY,EACZ,cAAc,EACd,iBAAiB,EACjB,YAAY,EACZ,YAAY,EACZ,UAAU,EACV,cAAc,EACd,QAAQ,EACR,aAAa,EACb,UAAU,EACV,eAAe,EACf,YAAY,EACZ,UAAU,EACV,eAAe,GAChB,MAAM,aAAa,CAAC;AAKrB,OAAO,EAAE,SAAS,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AAC5D,YAAY,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAOjD,OAAO,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AACtC,YAAY,EACV,WAAW,EACX,mBAAmB,EACnB,MAAM,EACN,SAAS,EACT,aAAa,EACb,IAAI,GACL,MAAM,YAAY,CAAC;AAIpB,YAAY,EACV,YAAY,EACZ,YAAY,EACZ,SAAS,EACT,cAAc,EACd,WAAW,EACX,cAAc,EACd,SAAS,EACT,cAAc,EACd,WAAW,EACX,QAAQ,EACR,aAAa,EACb,aAAa,EACb,cAAc,GACf,MAAM,aAAa,CAAC;AAErB,YAAY,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AASA,OAAO,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAC5C,YAAY,EACV,aAAa,EACb,iBAAiB,EACjB,cAAc,GACf,MAAM,cAAc,CAAC;AAKtB,OAAO,EACL,gBAAgB,EAChB,YAAY,EACZ,kBAAkB,EAClB,qBAAqB,EACrB,QAAQ,EACR,OAAO,EACP,MAAM,EAMN,gBAAgB,EAChB,gBAAgB,GACjB,MAAM,aAAa,CAAC;AACrB,YAAY,EACV,SAAS,EACT,WAAW,EACX,eAAe,EACf,SAAS,EACT,YAAY,EACZ,cAAc,EACd,iBAAiB,EACjB,YAAY,EACZ,YAAY,EACZ,UAAU,EACV,cAAc,EACd,QAAQ,EACR,aAAa,EACb,UAAU,EACV,eAAe,EACf,QAAQ,EACR,SAAS,EACT,YAAY,EACZ,UAAU,EACV,eAAe,GAChB,MAAM,aAAa,CAAC;AAKrB,OAAO,EAAE,SAAS,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AAC5D,YAAY,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAOjD,OAAO,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AACtC,YAAY,EACV,WAAW,EACX,mBAAmB,EACnB,MAAM,EACN,SAAS,EACT,aAAa,EACb,IAAI,GACL,MAAM,YAAY,CAAC;AAIpB,YAAY,EACV,YAAY,EACZ,YAAY,EACZ,SAAS,EACT,cAAc,EACd,WAAW,EACX,cAAc,EACd,SAAS,EACT,cAAc,EACd,WAAW,EACX,QAAQ,EACR,aAAa,EACb,aAAa,EACb,cAAc,GACf,MAAM,aAAa,CAAC;AAErB,YAAY,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"project.d.ts","sourceRoot":"","sources":["../src/project.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,cAAc,CAAC;AAEzC,OAAO,EAGL,KAAK,UAAU,EAChB,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EAYL,KAAK,eAAe,EAEpB,KAAK,QAAQ,EAEb,KAAK,YAAY,EACjB,KAAK,UAAU,EAEhB,MAAM,aAAa,CAAC;AAErB;;;;;;GAMG;AACH,MAAM,MAAM,aAAa,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;AAEpD,+DAA+D;AAC/D,MAAM,MAAM,cAAc,GAAG;IAAE,EAAE,EAAE,MAAM,CAAA;CAAE,GAAG,aAAa,CAAC;AAE5D;;;;;;;;;;GAUG;AACH,MAAM,WAAW,iBAAkB,SAAQ,OAAO,CAChD,QAAQ,CAAC,MAAM,CAAC,eAAe,EAAE,MAAM,CAAC,CAAC,CAC1C;IACC,4CAA4C;IAC5C,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAC;CAC3B;AAKD;;;;;;;;;;;;;GAaG;AACH,wBAAgB,eAAe,CAC7B,IAAI,EAAE,UAAU,EAChB,UAAU,EAAE,UAAU,EACtB,MAAM,CAAC,EAAE,YAAY,EACrB,OAAO,GAAE,iBAAyC,GACjD,cAAc,CAgBhB;AA2ED;;;;;;;;;;;;GAYG;AACH,wBAAuB,YAAY,CACjC,KAAK,EAAE,QAAQ,CAAC,IAAI,CAAC,EACrB,KAAK,EAAE,SAAS,MAAM,EAAE,EACxB,MAAM,EAAE,YAAY,EACpB,UAAU,EAAE,QAAQ,EACpB,OAAO,GAAE,iBAAyC,GACjD,aAAa,CAAC,cAAc,CAAC,CAW/B"}
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,EAiBL,KAAK,eAAe,EAEpB,KAAK,QAAQ,EAEb,KAAK,YAAY,EACjB,KAAK,UAAU,EAEhB,MAAM,aAAa,CAAC;AAErB;;;;;;GAMG;AACH,MAAM,MAAM,aAAa,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;AAEpD,+DAA+D;AAC/D,MAAM,MAAM,cAAc,GAAG;IAAE,EAAE,EAAE,MAAM,CAAA;CAAE,GAAG,aAAa,CAAC;AAE5D;;;;;;;;;;GAUG;AACH,MAAM,WAAW,iBAAkB,SAAQ,OAAO,CAChD,QAAQ,CAAC,MAAM,CAAC,eAAe,EAAE,MAAM,CAAC,CAAC,CAC1C;IACC,4CAA4C;IAC5C,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAC;CAC3B;AAKD;;;;;;;;;;;;;GAaG;AACH,wBAAgB,eAAe,CAC7B,IAAI,EAAE,UAAU,EAChB,UAAU,EAAE,UAAU,EACtB,MAAM,CAAC,EAAE,YAAY,EACrB,OAAO,GAAE,iBAAyC,GACjD,cAAc,CAgBhB;AAmHD;;;;;;;;;;;;GAYG;AACH,wBAAuB,YAAY,CACjC,KAAK,EAAE,QAAQ,CAAC,IAAI,CAAC,EACrB,KAAK,EAAE,SAAS,MAAM,EAAE,EACxB,MAAM,EAAE,YAAY,EACpB,UAAU,EAAE,QAAQ,EACpB,OAAO,GAAE,iBAAyC,GACjD,aAAa,CAAC,cAAc,CAAC,CAc/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, inlineFramingDepth, irAlias, isAbsoluteIri, isInternalField, isInlineReference, isoToUnixSeconds, physicalFields, referenceTypeNamed, } from './schema.js';
3
+ import { assertTypeInSchema, displayFieldName, documentKeyOf, fieldNamed, inheritedFacetKeys, inlineFramingDepth, irAlias, isAbsoluteIri, isInternalField, isInlineReference, isoToUnixSeconds, labelSourceNameOf, physicalFields, referenceTypeNamed, rootTypeNamed, } from './schema.js';
4
4
  /** The context a projection runs with when its caller supplied none. */
5
5
  const NO_PROJECTION_CONTEXT = {};
6
6
  /**
@@ -68,7 +68,7 @@ function pruneInternalFields(document, searchType, schema) {
68
68
  * once the whole nested structure is projected, so a derive at any depth can
69
69
  * still read an internal field before it is removed. */
70
70
  function projectFields(node, searchType, schema, context) {
71
- const id = documentKey(node);
71
+ const id = documentIdOf(node, searchType);
72
72
  const document = id === undefined ? {} : { id };
73
73
  for (const field of searchType.fields) {
74
74
  applyField(document, node, field, searchType, schema, context);
@@ -88,6 +88,33 @@ function documentKey(node) {
88
88
  const id = node['@id'];
89
89
  return typeof id === 'string' && isAbsoluteIri(id) ? id : undefined;
90
90
  }
91
+ /**
92
+ * The `id` a node is projected under: its own IRI, unless its type declares a
93
+ * {@link RootType.key} – then the key its key field carries
94
+ * ({@link documentKeyOf}). The candidates are read off the frame like any other
95
+ * field value, under the key field’s own {@link irAlias IR Alias}, because the
96
+ * key field *is* an ordinary declared field: the extraction already emits it,
97
+ * and a reader transform that repairs reference values has already run on it.
98
+ *
99
+ * The key is assigned before any `derive` runs, so a derive sees the key and
100
+ * never the node IRI; a deployment that wants the node IRI declares a plain
101
+ * `idOnly` reference over the same path.
102
+ */
103
+ function documentIdOf(node, searchType) {
104
+ const nodeIri = documentKey(node);
105
+ if (nodeIri === undefined || searchType.key === undefined) {
106
+ return nodeIri;
107
+ }
108
+ return documentKeyOf(searchType, nodeIri, keyCandidatesOf(node, searchType, searchType.key.field));
109
+ }
110
+ /** The raw values a node carries under a type’s key field – untransformed, as
111
+ * {@link documentKeyOf} expects them. Empty for a node the key field matched
112
+ * nothing on. The field itself is guaranteed declared by `searchSchema`, which
113
+ * is what validates a `key` at all. */
114
+ function keyCandidatesOf(node, searchType, name) {
115
+ const keyField = fieldNamed(searchType, name);
116
+ return irisOf(node, irAlias(searchType, keyField));
117
+ }
91
118
  /**
92
119
  * Project a single type over a known set of `roots` – the per-type, roots-given
93
120
  * projection. The roots are supplied by the caller (the pipeline selector)
@@ -106,8 +133,11 @@ export async function* projectRoots(quads, roots, schema, searchType, context =
106
133
  const index = buildSubjectIndex(quads);
107
134
  // Distinct roots only. A selector may return an IRI more than once – a
108
135
  // non-`DISTINCT` `SELECT` over a one-to-many join yields the same subject per
109
- // matched row – and a repeated root would otherwise frame and emit a
110
- // duplicate document under the same `id`.
136
+ // matched row – and framing a root twice would do the same work twice for one
137
+ // node. Distinct roots may still legitimately share an `id`, where the type
138
+ // declares a `key` two of them carry: that is what a document key means, and
139
+ // it is the writer’s upsert – not the projection – that folds them into one
140
+ // document.
111
141
  const depth = inlineFramingDepth(schema, searchType);
112
142
  for await (const node of frameSubjects(index, [...new Set(roots)], depth)) {
113
143
  yield projectDocument(node, searchType, schema, context);
@@ -118,7 +148,7 @@ function applyField(document, node, field, searchType, schema, context) {
118
148
  // (`validateSearchType`): a projection value, a computed value, a graph path.
119
149
  if ((field.kind === 'keyword' || field.kind === 'reference') &&
120
150
  field.from !== undefined) {
121
- return applyProjectionValue(document, field, field.from, context);
151
+ return applyProjectionValue(document, field, field.from, context, schema);
122
152
  }
123
153
  if (field.derive !== undefined) {
124
154
  const derived = field.derive(document, context);
@@ -177,9 +207,9 @@ function applyField(document, node, field, searchType, schema, context) {
177
207
  case 'text':
178
208
  return applyText(document, langValuesOf(node, alias), field);
179
209
  case 'keyword':
180
- return applyFacet(document, literalsOf(node, alias), field);
210
+ return applyFacet(document, literalsOf(node, alias), field, schema);
181
211
  case 'reference':
182
- return applyFacet(document, irisOf(node, alias), field);
212
+ return applyFacet(document, referenceValues(node, alias, field, schema), field, schema);
183
213
  case 'integer':
184
214
  return setNumber(document, field.name, toInteger(firstLiteralOf(node, alias)));
185
215
  case 'number':
@@ -198,6 +228,53 @@ function applyField(document, node, field, searchType, schema, context) {
198
228
  }
199
229
  }
200
230
  }
231
+ /**
232
+ * The values a non-inline reference stores: the referents’ IRIs, **or their
233
+ * document keys** when the type the reference names declares a
234
+ * {@link RootType.key}. A `lookup`’s `target` and an `idOnly`’s `labelSource`
235
+ * both mean *this field holds ids of documents in that collection* (ADR 20) –
236
+ * the fact a label lookup and a join rely on – so storing the node IRI where
237
+ * the target keys on something else would break an invariant the schema already
238
+ * has. Rewriting a reference is LDE keeping it, not a new rule.
239
+ *
240
+ * That is also why the boundary is *naming the target*: a reference that names
241
+ * none ({@link labelSourceNameOf} → `undefined`) never claimed to hold a
242
+ * collection’s ids, and a `derive`d one produces its own values rather than
243
+ * reading a referent, so neither is re-keyed.
244
+ *
245
+ * The referent’s candidates are in the frame because framing already embeds a
246
+ * referenced node’s own triples at one hop, and the extraction adds the key
247
+ * field to that hop. A projection run without a `schema` cannot resolve the
248
+ * target, so it leaves the values as they are – exactly as it leaves an inline
249
+ * reference unprojected.
250
+ *
251
+ * The referring field’s own {@link ReferenceField.transform} still runs after
252
+ * this, in {@link applyFacet}: a `transform` transforms what the field stores,
253
+ * and for a keyed target that is the key. A `transform` written to repair the
254
+ * referent’s node IRIs therefore sees a key instead, which is why the two are
255
+ * worth declaring together only deliberately.
256
+ */
257
+ function referenceValues(node, alias, field, schema) {
258
+ const targetName = labelSourceNameOf(field);
259
+ if (schema === undefined || targetName === undefined) {
260
+ return irisOf(node, alias);
261
+ }
262
+ // Guaranteed declared: `searchSchema` validates that every named target
263
+ // resolves, and the projection only ever runs against a type of its schema.
264
+ const target = rootTypeNamed(schema, targetName);
265
+ if (target.key === undefined) {
266
+ return irisOf(node, alias);
267
+ }
268
+ const keyFieldName = target.key.field;
269
+ return valuesOf(node, alias)
270
+ .map((value) => {
271
+ const iri = iriString(value);
272
+ return iri === undefined
273
+ ? undefined
274
+ : documentKeyOf(target, iri, isObject(value) ? keyCandidatesOf(value, target, keyFieldName) : []);
275
+ })
276
+ .filter((value) => value !== undefined);
277
+ }
201
278
  /**
202
279
  * Project a text field. **Display** (when `output`) preserves *every* language
203
280
  * present – one label per language (accents preserved, untagged under `und`),
@@ -250,6 +327,16 @@ function foldedSearchValue(values) {
250
327
  * `keyword` reads literals; `reference` reads IRIs (the caller passes the
251
328
  * already-read raw values).
252
329
  *
330
+ * A reference inheriting a {@link FacetKeys facet policy} from the type it
331
+ * names ({@link inheritedFacetKeys}) also writes the `${name}_facet` companion
332
+ * the engine facets instead of the field: the subset of **what the field
333
+ * stores** that the policy admits. So it is written after the field’s own
334
+ * `transform` and the IRI filter, from the same `values` – for a keyed target
335
+ * those are already keys ({@link referenceValues}), which is what makes a
336
+ * predicate over document keys the right shape. The field itself keeps every
337
+ * value; only the facet narrows. An empty subset writes nothing, exactly as a
338
+ * path that matched nothing does.
339
+ *
253
340
  * **`array` decides the shape**, as it does for every other kind: a declared
254
341
  * `array` field stores a list, and a single-valued one stores the first value –
255
342
  * the graph may still carry several, exactly as it may carry several literals
@@ -260,7 +347,7 @@ function foldedSearchValue(values) {
260
347
  * engine rejects, or – where it does not type-check, as inside a nested
261
348
  * document – a value the API cannot serialize.
262
349
  */
263
- function applyFacet(document, raw, field) {
350
+ function applyFacet(document, raw, field, schema) {
264
351
  // A `reference` stores identity, so what it stores must be an IRI whatever
265
352
  // route the value arrived by. {@link iriString} guards the graph path, but a
266
353
  // `transform` runs after it and a `from` projection value never passes it at
@@ -270,18 +357,34 @@ function applyFacet(document, raw, field) {
270
357
  const transformed = field.transform ? raw.map(field.transform) : raw;
271
358
  const values = dedupe(referenced ? transformed.filter(isAbsoluteIri) : transformed);
272
359
  const folded = dedupe(values.map((value) => fold(value)));
273
- const searchField = physicalFields(field).search[0];
360
+ const names = physicalFields(field, schema);
361
+ const searchField = names.search[0];
362
+ const policy = inheritedFacetKeys(field, schema);
363
+ // `names.facet` is the companion exactly when a policy is inherited –
364
+ // physicalFields reads the same `inheritedFacetKeys`.
365
+ const facetField = names.facet;
366
+ // The companion is a subset of what the field STORES – for a single-valued
367
+ // field its first value, not the first admitted of all of them, or the facet
368
+ // would count a value no filter on the field can reproduce.
369
+ const stored = field.array === true ? values : values.slice(0, 1);
370
+ const admitted = policy === undefined ? [] : stored.filter(policy.only);
274
371
  if (field.array === true) {
275
- setArray(document, field.name, values);
372
+ setArray(document, field.name, stored);
276
373
  if (field.searchable) {
277
374
  setArray(document, searchField, folded);
278
375
  }
376
+ if (policy !== undefined) {
377
+ setArray(document, facetField, admitted);
378
+ }
279
379
  return;
280
380
  }
281
- setString(document, field.name, values[0]);
381
+ setString(document, field.name, stored[0]);
282
382
  if (field.searchable) {
283
383
  setString(document, searchField, folded[0]);
284
384
  }
385
+ if (policy !== undefined) {
386
+ setString(document, facetField, admitted[0]);
387
+ }
285
388
  }
286
389
  /**
287
390
  * Project a field declared over a {@link ProjectionContext} value: read the
@@ -294,13 +397,13 @@ function applyFacet(document, raw, field) {
294
397
  * pipeline (a test, a one-off) leaves the field absent rather than inventing a
295
398
  * placeholder, the same as a `path` that matched nothing.
296
399
  */
297
- function applyProjectionValue(document, field, from, context) {
400
+ function applyProjectionValue(document, field, from, context, schema) {
298
401
  // `ProjectionContext` is keyed by `ProjectionValue`, so the declaration reads
299
402
  // the context directly: a projection value that has no context key, or a
300
403
  // context key no declaration can name, is a compile error rather than a
301
404
  // silently unpopulated field.
302
405
  const value = context[from];
303
- applyFacet(document, value === undefined ? [] : [value], field);
406
+ applyFacet(document, value === undefined ? [] : [value], field, schema);
304
407
  }
305
408
  /**
306
409
  * Project an inline reference: the referent node(s) embedded under the field’s
package/dist/schema.d.ts CHANGED
@@ -321,6 +321,90 @@ export interface SearchTypeBase {
321
321
  readonly labelField?: string;
322
322
  readonly fields: readonly SearchField[];
323
323
  }
324
+ /**
325
+ * Which declared field of a {@link RootType} holds its **document key**, when
326
+ * that key is not the node’s own IRI. It has the shape of
327
+ * {@link SearchTypeBase.labelField} – *which field is the label* – and says
328
+ * *which field holds the key*: a statement about the search document, never
329
+ * about the world, so the words a deployment reaches for (identity, alignment,
330
+ * canonical, authority) stay in the deployment’s own schema comments.
331
+ *
332
+ * Two things that already hold of keys then hold here too, without a new rule:
333
+ * several nodes sharing a key are **one document** (the writer upserts by `id`,
334
+ * so a deployment that wants particular content on the merged document attaches
335
+ * a transform, and one that does not gets last-writer-wins), and a reference
336
+ * into a keyed type stores the **target’s key** – a `lookup`/`labelSource`
337
+ * already means *this field holds ids of documents in that collection*
338
+ * (ADR 20), which storing the node IRI would break.
339
+ *
340
+ * See [ADR 22](../../docs/decisions/0022-key-a-root-type-on-a-declared-field.md).
341
+ */
342
+ export interface KeyField {
343
+ /**
344
+ * The `name` of a declared field of this type whose values are the key
345
+ * candidates: a path-bearing, `array` {@link ReferenceField} that is not
346
+ * `inline` ({@link searchSchema} validates all four). Being an ordinary field
347
+ * is the point – its extraction branch already exists, a reader transform
348
+ * that repairs reference values covers it the day it is declared, and its own
349
+ * {@link ReferenceField.transform} is where IRI normalisation lives, so two
350
+ * spellings of one IRI become one candidate before anything chooses among
351
+ * them.
352
+ *
353
+ * A transform that **replaces** a root’s quads must re-emit this field, the
354
+ * existing rule that *a field the document needs must be in the stream*
355
+ * applied to one more field; a transform that only adds never meets it.
356
+ */
357
+ readonly field: string;
358
+ /**
359
+ * Choose the key among the candidates – the key field’s values after its
360
+ * `transform`, IRI-filtered, deduplicated and sorted, so the default is
361
+ * deterministic whatever order the CONSTRUCT returned them in. Return one of
362
+ * them, or `undefined` to keep the node’s own IRI; anything else throws at
363
+ * projection. Not consulted for a node whose key field is empty.
364
+ *
365
+ * Must be **pure**: the same function keys the document and every reference
366
+ * to it, so a `pick` that consulted the network or the clock could key the
367
+ * two differently and leave a reference dangling. LDE never inspects an
368
+ * IRI’s shape – it asks.
369
+ *
370
+ * @default the first candidate
371
+ */
372
+ readonly pick?: (candidates: readonly string[]) => string | undefined;
373
+ }
374
+ /**
375
+ * A {@link RootType}’s **facet policy**: which of its documents get a facet
376
+ * bucket, as a predicate over the document key. Declared once, on the type,
377
+ * and inherited by every facetable reference that *names* the type – a
378
+ * `lookup`’s `target`, an `idOnly`’s `labelSource` ({@link labelSourceNameOf})
379
+ * – because *which of a type’s ids deserve a bucket* is a fact about the type,
380
+ * not about each field that points at it. A per-field policy would be one rule
381
+ * declared N times, and forgetting one would silently reintroduce the buckets
382
+ * on that facet alone.
383
+ *
384
+ * Only the facet narrows. The referring field keeps **every** value: a
385
+ * document still displays the excluded referent, and a `where` filter on its
386
+ * IRI still matches exactly. *Facets are discovery, filters are exact.*
387
+ *
388
+ * The mechanism is a second physical field per inheriting facet –
389
+ * `${name}_facet`, holding the admitted subset ({@link physicalFields}) – which
390
+ * the engine facets instead of the field itself, so the facet is exact under
391
+ * any bucket cap: the engine never sees an excluded value. Declaring a policy
392
+ * therefore changes the collection definition of every type that references
393
+ * this one; an engine adapter that reuses a live collection must notice.
394
+ *
395
+ * **The failure mode is silence.** A predicate that admits none of a type’s
396
+ * keys – a type whose alignments are not what it tests for, or stored values
397
+ * that are still node IRIs because the type was keyed after its references
398
+ * were indexed – empties every facet referencing the type without an error.
399
+ * Give each type its own predicate rather than reuse another’s, and key a type
400
+ * ({@link RootType.key}) before declaring a policy over its keys.
401
+ */
402
+ export interface FacetKeys {
403
+ /** Whether a document key gets a bucket. Must be **pure**: it is applied per
404
+ * document at projection time, so an impure one would bucket one document
405
+ * and not its twin. */
406
+ readonly only: (id: string) => boolean;
407
+ }
324
408
  /**
325
409
  * A **Root Type**: a {@link SearchType} that is indexed. It declares a `class`,
326
410
  * roots are selected for it, a Writer owns a collection for it, and the
@@ -333,6 +417,13 @@ export interface RootType extends SearchTypeBase {
333
417
  * key a {@link SearchSchema} maps this type under. Its presence is what makes
334
418
  * a type a Root Type – and so what gives it a collection. */
335
419
  readonly class: string;
420
+ /** Which declared field holds this type’s document key, when it is not the
421
+ * node’s own IRI ({@link KeyField}, {@link documentKeyOf}). */
422
+ readonly key?: KeyField;
423
+ /** Which of this type’s documents get a facet bucket, on every facetable
424
+ * reference that names this type ({@link FacetKeys}). Unset, every
425
+ * document does. */
426
+ readonly facetKeys?: FacetKeys;
336
427
  }
337
428
  /**
338
429
  * A **Reference Type**: a {@link SearchType} reached only through an
@@ -350,6 +441,13 @@ export interface ReferenceType extends SearchTypeBase {
350
441
  * {@link SearchField} discriminates by `kind`: an indexed Reference Type
351
442
  * fails to compile, not at run time. */
352
443
  readonly class?: never;
444
+ /** A Reference Type has no document key to declare: it is nested inside its
445
+ * referrer rather than keyed in a collection of its own. `never` for the
446
+ * same reason `class` is. */
447
+ readonly key?: never;
448
+ /** Nothing references a Reference Type by id, so there is no facet for a
449
+ * policy to narrow. `never` for the same reason `key` is. */
450
+ readonly facetKeys?: never;
353
451
  }
354
452
  /**
355
453
  * One type’s complete search declaration: its logical API `name`, the queryable
@@ -420,6 +518,19 @@ export declare function searchSchema<const Types extends readonly SearchType[]>(
420
518
  * resolves an inline reference’s referent shape through it.
421
519
  */
422
520
  export declare function referenceTypeNamed(schema: SearchSchema, name: string): ReferenceType | undefined;
521
+ /**
522
+ * The {@link RootType} a declaration names – a `lookup`’s `target`, an
523
+ * `idOnly`’s {@link ReferenceField.labelSource} ({@link labelSourceNameOf}), a
524
+ * join edge – or `undefined` when the schema declares no Root Type by that
525
+ * name. The one reading of *which type does this point at*, so the projection
526
+ * (which re-keys a reference through its target’s {@link KeyField}), the
527
+ * label-source validation and an adapter’s lookup cannot resolve a target
528
+ * differently.
529
+ *
530
+ * Complements {@link referenceTypeNamed}: the two name indexes are disjoint,
531
+ * because {@link searchSchema} rejects a name declared twice.
532
+ */
533
+ export declare function rootTypeNamed(schema: SearchSchema, name: string): RootType | undefined;
423
534
  /** Whether a field is an inline reference – a {@link ReferenceField} whose
424
535
  * `ref` carries its referent’s projected fields ({@link ReferenceType}). */
425
536
  export declare function isInlineReference(field: SearchField): field is ReferenceField & {
@@ -495,7 +606,7 @@ export declare function labelSourceNameOf(field: ReferenceField): string | undef
495
606
  */
496
607
  export interface SearchTypeIssue {
497
608
  readonly field: string;
498
- readonly reason: 'duplicate-field-name' | 'invalid-field-name' | 'unknown-kind' | 'invalid-locale' | 'missing-ref' | 'missing-ref-type-name' | 'ref-not-allowed' | 'text-requires-locales' | 'locales-not-allowed' | 'facet-ranges-not-allowed' | 'searchable-not-allowed' | 'transform-not-allowed' | 'derive-with-path' | 'from-not-allowed' | 'from-with-path' | 'from-with-derive' | 'from-with-inline-ref' | 'unknown-projection-value' | 'duplicate-projection-value' | 'text-not-filterable' | 'text-not-facetable' | 'joinable-not-allowed' | 'joinable-without-label-source' | 'joinable-with-inline-ref' | 'reserved-field-name';
609
+ readonly reason: 'duplicate-field-name' | 'invalid-field-name' | 'unknown-kind' | 'invalid-locale' | 'missing-ref' | 'missing-ref-type-name' | 'ref-not-allowed' | 'text-requires-locales' | 'locales-not-allowed' | 'facet-ranges-not-allowed' | 'searchable-not-allowed' | 'transform-not-allowed' | 'derive-with-path' | 'from-not-allowed' | 'from-with-path' | 'from-with-derive' | 'from-with-inline-ref' | 'unknown-projection-value' | 'duplicate-projection-value' | 'text-not-filterable' | 'text-not-facetable' | 'joinable-not-allowed' | 'joinable-without-label-source' | 'joinable-with-inline-ref' | 'reserved-field-name' | 'key-field-unknown' | 'key-field-not-reference' | 'key-field-without-path' | 'key-field-not-array' | 'key-field-inline' | 'key-pick-not-a-function' | 'key-not-allowed' | 'facet-keys-only-not-a-function' | 'facet-keys-not-allowed';
499
610
  }
500
611
  /**
501
612
  * The reserved logical name of a document’s IRI – the one field every indexed
@@ -565,7 +676,13 @@ export declare const OR_KEY = "or";
565
676
  * field has exactly one), is not an `inline` reference (a projection value is
566
677
  * a bare IRI, with no referent to carry fields from), and no two fields
567
678
  * declare the same projection value – which would leave a consumer reading
568
- * the dataset off a declaration no rule picks between.
679
+ * the dataset off a declaration no rule picks between;
680
+ * - a {@link RootType.key} names a declared, `path`-bearing, `array`,
681
+ * non-`inline` `reference` field of this type, and its `pick` is a function
682
+ * ({@link keyIssues}); a {@link ReferenceType} declares no key at all;
683
+ * - a {@link RootType.facetKeys} policy’s `only` is a function
684
+ * ({@link facetKeysIssues}); a {@link ReferenceType} declares no policy at
685
+ * all.
569
686
  *
570
687
  * Pure and total: returns every issue rather than throwing;
571
688
  * {@link assertValidSearchType} is the throwing entry point.
@@ -594,6 +711,11 @@ export interface PhysicalFields {
594
711
  /** Per-locale folded sort keys `${name}_sort_${locale}` (localized text,
595
712
  * `sortable`); a non-localized field sorts on its own `name` field. */
596
713
  readonly sort: readonly string[];
714
+ /** The field a facet on this field reads, when `facetable`: the field’s own
715
+ * `name`, or the `${name}_facet` companion holding the subset its target’s
716
+ * {@link FacetKeys facet policy} admits. `undefined` for a field that is
717
+ * not facetable. */
718
+ readonly facet: string | undefined;
597
719
  }
598
720
  /**
599
721
  * The display fields of a localized `text` field are pattern-based, not
@@ -688,6 +810,38 @@ export declare function referenceFields(searchType: SearchType): readonly Refere
688
810
  export declare function datasetField(searchType: SearchType): (KeywordField | ReferenceField) | undefined;
689
811
  /** Look up a field by its logical name. */
690
812
  export declare function fieldNamed(searchType: SearchType, name: string): SearchField | undefined;
813
+ /**
814
+ * The **document key** of one node of `searchType`: what the projection writes
815
+ * as the document’s `id`, and what a reference into this type stores.
816
+ *
817
+ * The whole rule, in one place, so the projection and any transform that needs
818
+ * to know a node’s key read the same answer:
819
+ *
820
+ * 1. a type declaring no {@link RootType.key} keys on `nodeIri` – the node’s own
821
+ * IRI, as every type does today;
822
+ * 2. otherwise the key field’s `rawValues` become **candidates**: its
823
+ * {@link ReferenceField.transform} (where IRI normalisation lives, so two
824
+ * spellings of one IRI become one candidate), then the
825
+ * {@link isAbsoluteIri} filter, then dedupe and sort – so the default is
826
+ * deterministic whatever order the CONSTRUCT returned them in;
827
+ * 3. no candidate keys on `nodeIri`, without consulting {@link KeyField.pick};
828
+ * 4. otherwise {@link KeyField.pick} chooses (defaulting to the first
829
+ * candidate), and `undefined` keeps `nodeIri`.
830
+ *
831
+ * So a key is always either the node’s own IRI or an IRI the graph offered for
832
+ * that node – never one invented in between. A `pick` returning anything else
833
+ * throws, naming the node and the candidates: it is a bug in a pure function
834
+ * that keys both the document and every reference to it, and letting it through
835
+ * would key those two differently.
836
+ *
837
+ * @param nodeIri the node’s own IRI – the key it falls back to
838
+ * @param rawValues the key field’s values **as a reference field reads them** –
839
+ * a node’s `@id` or a bare-string value, before the field’s `transform`. A
840
+ * caller that reads them itself (a transform working on quads) must apply the
841
+ * same rule and skip a literal-valued object, or it will compute a key for a
842
+ * value the projection never saw and key the two differently.
843
+ */
844
+ export declare function documentKeyOf(searchType: RootType, nodeIri: string, rawValues: readonly string[]): string;
691
845
  /**
692
846
  * The **IR Alias** predicate for a field: `urn:lde:‹SearchType.name›/‹field.name›`.
693
847
  * The extraction CONSTRUCT emits a field’s value under this minted predicate, and
@@ -746,9 +900,28 @@ export declare function isoToUnixSeconds(iso: string): number | undefined;
746
900
  export declare function unixSecondsToIso(seconds: number): string;
747
901
  /**
748
902
  * Derive the **indexed** physical engine field names a declaration produces:
749
- * the per-locale `search`/`sort` fanout. A localized `text` field’s display
750
- * fields are pattern-based and not enumerated here – see {@link displayFieldName}
751
- * and its siblings.
752
- */
753
- export declare function physicalFields(field: SearchField): PhysicalFields;
903
+ * the per-locale `search`/`sort` fanout, and the field a `facet` reads. A
904
+ * localized `text` field’s display fields are pattern-based and not enumerated
905
+ * here – see {@link displayFieldName} and its siblings.
906
+ *
907
+ * The facet is the one member that depends on more than the declaration: a
908
+ * facetable reference whose target declares a {@link FacetKeys facet policy}
909
+ * facets a `${name}_facet` companion rather than itself, and only the `schema`
910
+ * can resolve that target ({@link inheritedFacetKeys}). Without one the field
911
+ * facets on its own name – the same reading the projection makes without a
912
+ * schema, where it cannot re-key a reference either.
913
+ */
914
+ export declare function physicalFields(field: SearchField, schema?: SearchSchema): PhysicalFields;
915
+ /**
916
+ * The {@link FacetKeys facet policy} a field inherits: the `facetKeys` of the
917
+ * Root Type it names ({@link labelSourceNameOf}), when the field is a facetable
918
+ * reference and the schema resolves that type. The boundary is *naming the
919
+ * target* – the same line along which a reference is re-keyed and a join is
920
+ * drawn – so a reference that names no type inherits nothing, whatever type
921
+ * its values happen to point at; and a `derive`d reference, which produces its
922
+ * own values rather than reading a referent, is re-keyed by nothing and so
923
+ * narrowed by nothing either. The one reading the projection (which writes the
924
+ * companion) and {@link physicalFields} (which names it) share.
925
+ */
926
+ export declare function inheritedFacetKeys(field: SearchField, schema: SearchSchema | undefined): FacetKeys | undefined;
754
927
  //# sourceMappingURL=schema.d.ts.map
@@ -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":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,iBAAiB,EAAE,MAAM,cAAc,CAAC;AAGrE;;;;;;GAMG;AACH,MAAM,MAAM,SAAS,GACnB,MAAM,GAAG,SAAS,GAAG,SAAS,GAAG,QAAQ,GAAG,SAAS,GAAG,MAAM,GAAG,WAAW,CAAC;AAE/E;oEACoE;AACpE,MAAM,MAAM,cAAc,GAAG,IAAI,GAAG,OAAO,GAAG,IAAI,CAAC;AAcnD;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,SAAS,GAAG,cAAc,GAAG,SAAS,CAE7E;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAmCG;AACH,MAAM,MAAM,WAAW,GACrB,SAAS,GAAG,YAAY,GAAG,cAAc,GAAG,YAAY,GAAG,YAAY,CAAC;AAE1E,qEAAqE;AACrE,MAAM,WAAW,eAAe;IAC9B;wCACoC;IACpC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB;;;;;;;;;;OAUG;IACH,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAC;IAC9B;+EAC2E;IAC3E,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC;IACvB;;;2DAGuD;IACvD,QAAQ,CAAC,KAAK,CAAC,EAAE,OAAO,CAAC;IACzB;;yCAEqC;IACrC,QAAQ,CAAC,QAAQ,CAAC,EAAE,OAAO,CAAC;IAC5B,gEAAgE;IAChE,QAAQ,CAAC,MAAM,CAAC,EAAE,OAAO,CAAC;IAC1B,yBAAyB;IACzB,QAAQ,CAAC,UAAU,CAAC,EAAE,OAAO,CAAC;IAC9B,iCAAiC;IACjC,QAAQ,CAAC,SAAS,CAAC,EAAE,OAAO,CAAC;IAC7B,qFAAqF;IACrF,QAAQ,CAAC,QAAQ,CAAC,EAAE,OAAO,CAAC;IAC5B;;;;;;;;;;;;;;;;;;;;;;;;;;OA0BG;IACH,QAAQ,CAAC,MAAM,CAAC,EAAE,CAChB,QAAQ,EAAE,aAAa,EACvB,OAAO,EAAE,iBAAiB,KACvB,OAAO,CAAC;CACd;AAED;;;;;;;;;;;;;GAaG;AACH,MAAM,MAAM,eAAe,GAAG,SAAS,CAAC;AAExC;kEACkE;AAClE,MAAM,WAAW,UAAU;IACzB,QAAQ,CAAC,UAAU,CAAC,EAAE;QAAE,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;KAAE,CAAC;CACnD;AAED;;;;;;;;;;;;GAYG;AACH,MAAM,WAAW,SAAU,SAAQ,eAAe,EAAE,UAAU;IAC5D,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,+EAA+E;IAC/E,QAAQ,CAAC,OAAO,EAAE,SAAS,MAAM,EAAE,CAAC;IACpC,QAAQ,CAAC,UAAU,CAAC,EAAE,KAAK,CAAC;IAC5B,QAAQ,CAAC,SAAS,CAAC,EAAE,KAAK,CAAC;IAC3B,QAAQ,CAAC,WAAW,CAAC,EAAE,KAAK,CAAC;CAC9B;AAED;gCACgC;AAChC,MAAM,WAAW,YAAa,SAAQ,eAAe,EAAE,UAAU;IAC/D,QAAQ,CAAC,IAAI,EAAE,SAAS,CAAC;IACzB,wEAAwE;IACxE,QAAQ,CAAC,SAAS,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,MAAM,CAAC;IAC/C,QAAQ,CAAC,WAAW,CAAC,EAAE,KAAK,CAAC;IAC7B;8CAC0C;IAC1C,QAAQ,CAAC,IAAI,CAAC,EAAE,eAAe,CAAC;CACjC;AAED;;;;;;;;;;;;;GAaG;AACH,MAAM,MAAM,iBAAiB,GACzB;IACE,QAAQ,CAAC,QAAQ,EAAE,QAAQ,CAAC;IAC5B;;;;;sCAKkC;IAClC,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,MAAM,CAAC,EAAE,KAAK,CAAC;CACzB,GACD;IAAE,QAAQ,CAAC,QAAQ,EAAE,QAAQ,CAAC;IAAC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;CAAE,GACxD;IAAE,QAAQ,CAAC,QAAQ,EAAE,QAAQ,CAAC;IAAC,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,CAAC;AAE/D,0EAA0E;AAC1E,MAAM,WAAW,cAAe,SAAQ,eAAe,EAAE,UAAU;IACjE,QAAQ,CAAC,IAAI,EAAE,WAAW,CAAC;IAC3B,uCAAuC;IACvC,QAAQ,CAAC,SAAS,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,MAAM,CAAC;IAC/C,QAAQ,CAAC,WAAW,CAAC,EAAE,KAAK,CAAC;IAC7B;;;oCAGgC;IAChC,QAAQ,CAAC,IAAI,CAAC,EAAE,eAAe,CAAC;IAChC;;;;;;;;OAQG;IACH,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAC;IAC9B;;;;;;;;;;;;;;;;;;;;;OAqBG;IACH,QAAQ,CAAC,QAAQ,CAAC,EAAE,OAAO,CAAC;IAC5B;;;;;;;;;;;;;OAaG;IACH,QAAQ,CAAC,GAAG,CAAC,EAAE,iBAAiB,CAAC;CAClC;AAED;;;;;;;GAOG;AACH,MAAM,WAAW,cAAc;IAC7B,QAAQ,CAAC,WAAW,CAAC,EAAE,SAAS,UAAU,EAAE,CAAC;CAC9C;AAED;;;;;GAKG;AACH,MAAM,WAAW,YAAa,SAAQ,eAAe,EAAE,cAAc;IACnE,QAAQ,CAAC,IAAI,EAAE,SAAS,GAAG,QAAQ,GAAG,MAAM,CAAC;IAC7C,QAAQ,CAAC,UAAU,CAAC,EAAE,KAAK,CAAC;CAC7B;AAED,0DAA0D;AAC1D,MAAM,WAAW,YAAa,SAAQ,eAAe;IACnD,QAAQ,CAAC,IAAI,EAAE,SAAS,CAAC;IACzB,QAAQ,CAAC,UAAU,CAAC,EAAE,KAAK,CAAC;IAC5B,QAAQ,CAAC,WAAW,CAAC,EAAE,KAAK,CAAC;CAC9B;AAED;;;;;GAKG;AACH,MAAM,WAAW,UAAU;IACzB,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,GAAG,CAAC,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,GAAG,CAAC,EAAE,MAAM,CAAC;CACvB;AAED,+DAA+D;AAC/D,MAAM,WAAW,cAAc;IAC7B;;;;+CAI2C;IAC3C,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB;;;;;;2DAMuD;IACvD,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,MAAM,EAAE,SAAS,WAAW,EAAE,CAAC;CACzC;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,MAAM,WAAW,QAAQ;IACvB;;;;;;;;;;;;;OAaG;IACH,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB;;;;;;;;;;;;;OAaG;IACH,QAAQ,CAAC,IAAI,CAAC,EAAE,CAAC,UAAU,EAAE,SAAS,MAAM,EAAE,KAAK,MAAM,GAAG,SAAS,CAAC;CACvE;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AACH,MAAM,WAAW,SAAS;IACxB;;4BAEwB;IACxB,QAAQ,CAAC,IAAI,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,OAAO,CAAC;CACxC;AAED;;;;;;GAMG;AACH,MAAM,WAAW,QAAS,SAAQ,cAAc;IAC9C;;kEAE8D;IAC9D,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB;oEACgE;IAChE,QAAQ,CAAC,GAAG,CAAC,EAAE,QAAQ,CAAC;IACxB;;yBAEqB;IACrB,QAAQ,CAAC,SAAS,CAAC,EAAE,SAAS,CAAC;CAChC;AAED;;;;;;;;;GASG;AACH,MAAM,WAAW,aAAc,SAAQ,cAAc;IACnD;;;6CAGyC;IACzC,QAAQ,CAAC,KAAK,CAAC,EAAE,KAAK,CAAC;IACvB;;kCAE8B;IAC9B,QAAQ,CAAC,GAAG,CAAC,EAAE,KAAK,CAAC;IACrB;kEAC8D;IAC9D,QAAQ,CAAC,SAAS,CAAC,EAAE,KAAK,CAAC;CAC5B;AAED;;;;;;GAMG;AACH,MAAM,MAAM,UAAU,GAAG,QAAQ,GAAG,aAAa,CAAC;AAElD;;kEAEkE;AAClE,MAAM,MAAM,UAAU,CAAC,KAAK,SAAS,SAAS,UAAU,EAAE,IAAI,OAAO,CACnE,KAAK,CAAC,MAAM,CAAC,EACb;IAAE,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAC3B,CAAC;AAEF;;;;;;;GAOG;AACH,wBAAgB,gBAAgB,CAAC,KAAK,CAAC,IAAI,SAAS,UAAU,EAC5D,UAAU,EAAE,IAAI,GACf,IAAI,CAEN;AAED;;;;;;;;;;;GAWG;AACH;;;uCAGuC;AACvC,MAAM,CAAC,OAAO,CAAC,MAAM,iBAAiB,EAAE,OAAO,MAAM,CAAC;AAEtD,MAAM,WAAW,YAAY,CAC3B,KAAK,SAAS,SAAS,UAAU,EAAE,GAAG,SAAS,UAAU,EAAE,CAC3D,SAAQ,WAAW,CAAC,MAAM,EAAE,UAAU,CAAC,KAAK,CAAC,CAAC;IAC9C,QAAQ,CAAC,CAAC,iBAAiB,CAAC,EAAE,IAAI,CAAC;CACpC;AAmCD;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,YAAY,CAAC,KAAK,CAAC,KAAK,SAAS,SAAS,UAAU,EAAE,EACpE,GAAG,KAAK,EAAE,KAAK,GACd,YAAY,CAAC,KAAK,CAAC,CAiDrB;AAED;;;;;GAKG;AACH,wBAAgB,kBAAkB,CAChC,MAAM,EAAE,YAAY,EACpB,IAAI,EAAE,MAAM,GACX,aAAa,GAAG,SAAS,CAE3B;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,aAAa,CAC3B,MAAM,EAAE,YAAY,EACpB,IAAI,EAAE,MAAM,GACX,QAAQ,GAAG,SAAS,CAEtB;AAED;6EAC6E;AAC7E,wBAAgB,iBAAiB,CAC/B,KAAK,EAAE,WAAW,GACjB,KAAK,IAAI,cAAc,GAAG;IAC3B,QAAQ,CAAC,GAAG,EAAE;QAAE,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,QAAQ,EAAE,QAAQ,CAAA;KAAE,CAAC;CAC1E,CAEA;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,mBAAmB,CACjC,MAAM,EAAE,YAAY,EACpB,KAAK,EAAE,WAAW,GACjB,aAAa,GAAG,SAAS,CAI3B;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,eAAe,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,CAEpE;AAED;;;;;;;GAOG;AACH,wBAAgB,kBAAkB,CAChC,MAAM,EAAE,YAAY,EACpB,UAAU,EAAE,UAAU,GACrB,MAAM,CAER;AA8JD;yCACyC;AACzC,eAAO,MAAM,mBAAmB,UAAU,CAAC;AAE3C;uDACuD;AACvD,wBAAgB,gBAAgB,CAAC,UAAU,EAAE,UAAU,GAAG,MAAM,CAE/D;AAED;;;;;;;GAOG;AACH,wBAAgB,YAAY,CAAC,UAAU,EAAE,UAAU,GAAG,SAAS,GAAG,SAAS,CAQ1E;AAED;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,cAAc,GAAG,MAAM,GAAG,SAAS,CAI3E;AAiED;;;;;GAKG;AACH,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,MAAM,EACX,sBAAsB,GACtB,oBAAoB,GACpB,cAAc,GACd,gBAAgB,GAChB,aAAa,GACb,uBAAuB,GACvB,iBAAiB,GACjB,uBAAuB,GACvB,qBAAqB,GACrB,0BAA0B,GAC1B,wBAAwB,GACxB,uBAAuB,GACvB,kBAAkB,GAClB,kBAAkB,GAClB,gBAAgB,GAChB,kBAAkB,GAClB,sBAAsB,GACtB,0BAA0B,GAC1B,4BAA4B,GAC5B,qBAAqB,GACrB,oBAAoB,GACpB,sBAAsB,GACtB,+BAA+B,GAC/B,0BAA0B,GAC1B,qBAAqB,GACrB,mBAAmB,GACnB,yBAAyB,GACzB,wBAAwB,GACxB,qBAAqB,GACrB,kBAAkB,GAClB,yBAAyB,GACzB,iBAAiB,GACjB,gCAAgC,GAChC,wBAAwB,CAAC;CAC9B;AAED;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,QAAQ,OAAO,CAAC;AAE7B;;;;;;;;;;;;;GAaG;AACH,eAAO,MAAM,OAAO,QAAQ,CAAC;AAC7B,eAAO,MAAM,MAAM,OAAO,CAAC;AAyC3B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiDG;AACH,wBAAgB,kBAAkB,CAChC,UAAU,EAAE,UAAU,GACrB,SAAS,eAAe,EAAE,CA8J5B;AA2FD;;;;;GAKG;AACH,wBAAgB,kBAAkB,CAChC,MAAM,EAAE,YAAY,EACpB,UAAU,EAAE,QAAQ,GACnB,IAAI,CAUN;AAED;+EAC+E;AAC/E,wBAAgB,qBAAqB,CAAC,UAAU,EAAE,UAAU,GAAG,IAAI,CAQlE;AAED;;;;;GAKG;AACH,MAAM,WAAW,cAAc;IAC7B;sEACkE;IAClE,QAAQ,CAAC,MAAM,EAAE,SAAS,MAAM,EAAE,CAAC;IACnC;4EACwE;IACxE,QAAQ,CAAC,IAAI,EAAE,SAAS,MAAM,EAAE,CAAC;IACjC;;;yBAGqB;IACrB,QAAQ,CAAC,KAAK,EAAE,MAAM,GAAG,SAAS,CAAC;CACpC;AAED;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,SAAS,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,CAEvE;AAED;;;;;;GAMG;AACH,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,SAAS,GAAG,MAAM,GAAG,SAAS,CAExE;AAED;;;;;;GAMG;AACH,wBAAgB,aAAa,CAC3B,KAAK,EAAE,SAAS,EAChB,GAAG,EAAE,MAAM,GACV,MAAM,GAAG,SAAS,CAOpB;AAED;;;;GAIG;AACH,wBAAgB,gBAAgB,CAC9B,UAAU,EAAE,UAAU,GACrB,SAAS,CAAC,WAAW,GAAG;IACzB,QAAQ,CAAC,UAAU,EAAE;QAAE,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;KAAE,CAAC;CAClD,CAAC,EAAE,CAOH;AAED,8DAA8D;AAC9D,wBAAgB,eAAe,CAC7B,UAAU,EAAE,UAAU,GACrB,SAAS,WAAW,EAAE,CAExB;AAED,sDAAsD;AACtD,wBAAgB,gBAAgB,CAC9B,UAAU,EAAE,UAAU,GACrB,SAAS,WAAW,EAAE,CAExB;AAED,qEAAqE;AACrE,wBAAgB,cAAc,CAAC,UAAU,EAAE,UAAU,GAAG,SAAS,WAAW,EAAE,CAE7E;AAED,uEAAuE;AACvE,wBAAgB,YAAY,CAAC,UAAU,EAAE,UAAU,GAAG,SAAS,WAAW,EAAE,CAE3E;AAED;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAgB,eAAe,CAAC,KAAK,EAAE,WAAW,GAAG,OAAO,CAS3D;AAED,qFAAqF;AACrF,wBAAgB,eAAe,CAC7B,UAAU,EAAE,UAAU,GACrB,SAAS,cAAc,EAAE,CAE3B;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,YAAY,CAC1B,UAAU,EAAE,UAAU,GACrB,CAAC,YAAY,GAAG,cAAc,CAAC,GAAG,SAAS,CAM7C;AAED,2CAA2C;AAC3C,wBAAgB,UAAU,CACxB,UAAU,EAAE,UAAU,EACtB,IAAI,EAAE,MAAM,GACX,WAAW,GAAG,SAAS,CAEzB;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AACH,wBAAgB,aAAa,CAC3B,UAAU,EAAE,QAAQ,EACpB,OAAO,EAAE,MAAM,EACf,SAAS,EAAE,SAAS,MAAM,EAAE,GAC3B,MAAM,CAgCR;AAOD;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,OAAO,CAAC,UAAU,EAAE,UAAU,EAAE,KAAK,EAAE,WAAW,GAAG,MAAM,CAE1E;AAED;;;;;;GAMG;AACH,wBAAgB,YAAY,CAC1B,KAAK,EAAE,WAAW,GACjB,KAAK,IAAI,YAAY,GAAG;IAAE,QAAQ,CAAC,WAAW,EAAE,SAAS,UAAU,EAAE,CAAA;CAAE,CAEzE;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,aAAa,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAEpD;AAOD;;;;;;GAMG;AACH,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAQhE;AAyBD,+EAA+E;AAC/E,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAExD;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,cAAc,CAC5B,KAAK,EAAE,WAAW,EAClB,MAAM,CAAC,EAAE,YAAY,GACpB,cAAc,CAwBhB;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,kBAAkB,CAChC,KAAK,EAAE,WAAW,EAClB,MAAM,EAAE,YAAY,GAAG,SAAS,GAC/B,SAAS,GAAG,SAAS,CAavB"}
package/dist/schema.js CHANGED
@@ -36,6 +36,17 @@ export function defineSearchType(searchType) {
36
36
  * through {@link referenceTypeNamed}.
37
37
  */
38
38
  const referenceTypesBySchema = new WeakMap();
39
+ /**
40
+ * The Root Type name index each {@link SearchSchema} carries alongside its
41
+ * class-keyed map. A schema is keyed by `class` because that is what a selector
42
+ * and a Writer address it by, while every declaration that points at another
43
+ * type – a `lookup`’s `target`, an `idOnly`’s `labelSource`, a join edge –
44
+ * names it. Kept beside the map rather than rebuilt per consumer, so the
45
+ * projection, the label-source validation and an adapter’s lookup resolve a
46
+ * target the same way ({@link rootTypeNamed}) instead of each maintaining a
47
+ * by-name map of its own.
48
+ */
49
+ const rootTypesBySchema = new WeakMap();
39
50
  /** Whether a declared type is a {@link RootType} (declares a `class`) rather
40
51
  * than a {@link ReferenceType}. */
41
52
  function isRootType(searchType) {
@@ -80,12 +91,16 @@ export function searchSchema(...types) {
80
91
  .map((searchType) => [searchType.name, searchType]));
81
92
  assertResolvableInlineReferences(types, referenceTypes);
82
93
  assertServiceableNestedFields(referenceTypes);
83
- assertResolvableLabelSources(types);
84
- // The one blessed cast: only this validated constructor mints the brand.
85
- const schema = new Map(types
86
- .filter(isRootType)
87
- .map((searchType) => [searchType.class, searchType]));
94
+ // The one blessed cast: only this validated constructor mints the brand. Built
95
+ // BEFORE the schema-wide assertions that resolve a type by name, so they can
96
+ // use `rootTypeNamed` – the same reading the projection and an adapter's
97
+ // lookup use – rather than a by-name map of their own. The schema stays local
98
+ // until every assertion has passed, so an invalid one is never observable.
99
+ const rootTypes = types.filter(isRootType);
100
+ const schema = new Map(rootTypes.map((searchType) => [searchType.class, searchType]));
88
101
  referenceTypesBySchema.set(schema, referenceTypes);
102
+ rootTypesBySchema.set(schema, new Map(rootTypes.map((searchType) => [searchType.name, searchType])));
103
+ assertResolvableLabelSources(types, schema);
89
104
  // Build the join graph eagerly and discard it: it is cached per schema, and
90
105
  // building it is what enforces the schema-wide join rules (one joinable
91
106
  // reference per target, every target an indexed Root Type, no cycles). A
@@ -103,6 +118,21 @@ export function searchSchema(...types) {
103
118
  export function referenceTypeNamed(schema, name) {
104
119
  return referenceTypesBySchema.get(schema)?.get(name);
105
120
  }
121
+ /**
122
+ * The {@link RootType} a declaration names – a `lookup`’s `target`, an
123
+ * `idOnly`’s {@link ReferenceField.labelSource} ({@link labelSourceNameOf}), a
124
+ * join edge – or `undefined` when the schema declares no Root Type by that
125
+ * name. The one reading of *which type does this point at*, so the projection
126
+ * (which re-keys a reference through its target’s {@link KeyField}), the
127
+ * label-source validation and an adapter’s lookup cannot resolve a target
128
+ * differently.
129
+ *
130
+ * Complements {@link referenceTypeNamed}: the two name indexes are disjoint,
131
+ * because {@link searchSchema} rejects a name declared twice.
132
+ */
133
+ export function rootTypeNamed(schema, name) {
134
+ return rootTypesBySchema.get(schema)?.get(name);
135
+ }
106
136
  /** Whether a field is an inline reference – a {@link ReferenceField} whose
107
137
  * `ref` carries its referent’s projected fields ({@link ReferenceType}). */
108
138
  export function isInlineReference(field) {
@@ -311,13 +341,13 @@ export function labelSourceNameOf(field) {
311
341
  * can actually serve labels ({@link labelFieldOf}). Checked schema-wide,
312
342
  * because a single declaration cannot see its siblings.
313
343
  *
314
- * That leaves only a {@link RootType}, without naming one: a label field is
315
- * `searchable`, and {@link assertServiceableNestedFields} already rejects a
316
- * `searchable` field on a Reference Type – so a Reference Type can never serve
317
- * labels, and a resolved label always has a collection to come from.
344
+ * Resolved through {@link rootTypeNamed}, which is exactly the set that can
345
+ * serve labels: a label field is `searchable`, and
346
+ * {@link assertServiceableNestedFields} – which runs first – already rejects a
347
+ * `searchable` field on a Reference Type, so a resolved label always has a
348
+ * collection to come from.
318
349
  */
319
- function assertResolvableLabelSources(types) {
320
- const byName = new Map(types.map((searchType) => [searchType.name, searchType]));
350
+ function assertResolvableLabelSources(types, schema) {
321
351
  for (const searchType of types) {
322
352
  for (const field of searchType.fields) {
323
353
  const labelSource = field
@@ -339,9 +369,15 @@ function assertResolvableLabelSources(types) {
339
369
  if (sourceName === undefined) {
340
370
  continue;
341
371
  }
342
- const source = byName.get(sourceName);
372
+ const source = rootTypeNamed(schema, sourceName);
343
373
  if (source === undefined) {
344
- throw new Error(`Reference “${searchType.name}.${field.name}” names unknown label source “${sourceName}”; declare a SearchType with that name.`);
374
+ // A name that IS declared, just not as a Root Type, is the confusing
375
+ // case: telling the author to declare a type they already declared
376
+ // would send them looking in the wrong place. Only a Root Type has a
377
+ // collection to resolve against, so name that instead.
378
+ throw new Error(referenceTypeNamed(schema, sourceName) === undefined
379
+ ? `Reference “${searchType.name}.${field.name}” names unknown label source “${sourceName}”; declare a SearchType with that name.`
380
+ : `Reference “${searchType.name}.${field.name}” names label source “${sourceName}”, which is a Reference Type; a label source must be a Root Type, since a resolved label is read from that type’s own collection.`);
345
381
  }
346
382
  if (labelFieldOf(source) === undefined) {
347
383
  throw new Error(`Reference “${searchType.name}.${field.name}” uses label source “${sourceName}”, which must declare an output, searchable text field “${labelFieldNameOf(source)}”.`);
@@ -449,7 +485,13 @@ const LOCALE_PATTERN = /^[A-Za-z0-9]+(-[A-Za-z0-9]+)*$/;
449
485
  * field has exactly one), is not an `inline` reference (a projection value is
450
486
  * a bare IRI, with no referent to carry fields from), and no two fields
451
487
  * declare the same projection value – which would leave a consumer reading
452
- * the dataset off a declaration no rule picks between.
488
+ * the dataset off a declaration no rule picks between;
489
+ * - a {@link RootType.key} names a declared, `path`-bearing, `array`,
490
+ * non-`inline` `reference` field of this type, and its `pick` is a function
491
+ * ({@link keyIssues}); a {@link ReferenceType} declares no key at all;
492
+ * - a {@link RootType.facetKeys} policy’s `only` is a function
493
+ * ({@link facetKeysIssues}); a {@link ReferenceType} declares no policy at
494
+ * all.
453
495
  *
454
496
  * Pure and total: returns every issue rather than throwing;
455
497
  * {@link assertValidSearchType} is the throwing entry point.
@@ -596,8 +638,84 @@ export function validateSearchType(searchType) {
596
638
  }
597
639
  }
598
640
  }
641
+ issues.push(...keyIssues(searchType), ...facetKeysIssues(searchType));
642
+ return issues;
643
+ }
644
+ /**
645
+ * The issues a type’s {@link RootType.key} declaration carries, each filed
646
+ * under the field name the declaration names – so a message reads like every
647
+ * other one, naming the field the rule is about rather than a member.
648
+ *
649
+ * The key field must be an ordinary declared field the extraction already
650
+ * reads and the projection already applies a `transform` to: a `reference`
651
+ * (candidates are IRIs), with a `path` (a key read from the graph, not one
652
+ * derived from a document that is keyed already), `array` (a node may offer
653
+ * several candidates, and a single-valued field would drop all but the first
654
+ * before `pick` ever saw them), and not `inline` (which carries the referent’s
655
+ * fields rather than its IRI, so it offers no candidate at all).
656
+ */
657
+ function keyIssues(searchType) {
658
+ const key = searchType.key;
659
+ if (key === undefined) {
660
+ return [];
661
+ }
662
+ const issue = (reason) => ({
663
+ field: key.field,
664
+ reason,
665
+ });
666
+ // A Reference Type is nested inside its referrer, never keyed in a collection
667
+ // of its own – so a key on one states a rule nothing could apply.
668
+ if (searchType.class === undefined) {
669
+ return [issue('key-not-allowed')];
670
+ }
671
+ const issues = [];
672
+ if (key.pick !== undefined && typeof key.pick !== 'function') {
673
+ issues.push(issue('key-pick-not-a-function'));
674
+ }
675
+ const field = fieldNamed(searchType, key.field);
676
+ if (field === undefined) {
677
+ issues.push(issue('key-field-unknown'));
678
+ return issues;
679
+ }
680
+ if (field.kind !== 'reference') {
681
+ issues.push(issue('key-field-not-reference'));
682
+ return issues;
683
+ }
684
+ if (field.path === undefined) {
685
+ issues.push(issue('key-field-without-path'));
686
+ }
687
+ if (field.array !== true) {
688
+ issues.push(issue('key-field-not-array'));
689
+ }
690
+ if (field.ref?.strategy === 'inline') {
691
+ issues.push(issue('key-field-inline'));
692
+ }
599
693
  return issues;
600
694
  }
695
+ /**
696
+ * The issues a type’s {@link RootType.facetKeys} declaration carries, filed
697
+ * under the member’s name: the policy names no field – it is a rule about the
698
+ * type’s own keys, applied through other types’ fields – so there is no field
699
+ * name to file it under.
700
+ */
701
+ function facetKeysIssues(searchType) {
702
+ const facetKeys = searchType.facetKeys;
703
+ if (facetKeys === undefined) {
704
+ return [];
705
+ }
706
+ const issue = (reason) => ({
707
+ field: 'facetKeys',
708
+ reason,
709
+ });
710
+ // Nothing references a Reference Type by id, so a policy on one narrows no
711
+ // facet – it states a rule nothing could apply.
712
+ if (searchType.class === undefined) {
713
+ return [issue('facet-keys-not-allowed')];
714
+ }
715
+ return typeof facetKeys.only === 'function'
716
+ ? []
717
+ : [issue('facet-keys-only-not-a-function')];
718
+ }
601
719
  /**
602
720
  * Throw when `searchType` is not a member of `schema` – the port membership
603
721
  * guard every engine adapter applies before searching, so a query can never
@@ -750,6 +868,66 @@ export function datasetField(searchType) {
750
868
  export function fieldNamed(searchType, name) {
751
869
  return searchType.fields.find((field) => field.name === name);
752
870
  }
871
+ /**
872
+ * The **document key** of one node of `searchType`: what the projection writes
873
+ * as the document’s `id`, and what a reference into this type stores.
874
+ *
875
+ * The whole rule, in one place, so the projection and any transform that needs
876
+ * to know a node’s key read the same answer:
877
+ *
878
+ * 1. a type declaring no {@link RootType.key} keys on `nodeIri` – the node’s own
879
+ * IRI, as every type does today;
880
+ * 2. otherwise the key field’s `rawValues` become **candidates**: its
881
+ * {@link ReferenceField.transform} (where IRI normalisation lives, so two
882
+ * spellings of one IRI become one candidate), then the
883
+ * {@link isAbsoluteIri} filter, then dedupe and sort – so the default is
884
+ * deterministic whatever order the CONSTRUCT returned them in;
885
+ * 3. no candidate keys on `nodeIri`, without consulting {@link KeyField.pick};
886
+ * 4. otherwise {@link KeyField.pick} chooses (defaulting to the first
887
+ * candidate), and `undefined` keeps `nodeIri`.
888
+ *
889
+ * So a key is always either the node’s own IRI or an IRI the graph offered for
890
+ * that node – never one invented in between. A `pick` returning anything else
891
+ * throws, naming the node and the candidates: it is a bug in a pure function
892
+ * that keys both the document and every reference to it, and letting it through
893
+ * would key those two differently.
894
+ *
895
+ * @param nodeIri the node’s own IRI – the key it falls back to
896
+ * @param rawValues the key field’s values **as a reference field reads them** –
897
+ * a node’s `@id` or a bare-string value, before the field’s `transform`. A
898
+ * caller that reads them itself (a transform working on quads) must apply the
899
+ * same rule and skip a literal-valued object, or it will compute a key for a
900
+ * value the projection never saw and key the two differently.
901
+ */
902
+ export function documentKeyOf(searchType, nodeIri, rawValues) {
903
+ const key = searchType.key;
904
+ if (key === undefined) {
905
+ return nodeIri;
906
+ }
907
+ // Guaranteed a declared reference field by `searchSchema`, which is what
908
+ // validates a `key` at all.
909
+ const { transform } = fieldNamed(searchType, key.field);
910
+ const candidates = [
911
+ ...new Set((transform === undefined ? rawValues : rawValues.map(transform)).filter(isAbsoluteIri)),
912
+ ].sort();
913
+ if (candidates.length === 0) {
914
+ return nodeIri;
915
+ }
916
+ const picked = (key.pick ?? firstCandidate)(candidates);
917
+ if (picked === undefined) {
918
+ return nodeIri;
919
+ }
920
+ if (!candidates.includes(picked)) {
921
+ throw new Error(`The “pick” of “${searchType.name}.key” returned “${picked}” for <${nodeIri}>, which is not among its candidates (${candidates
922
+ .map((candidate) => `<${candidate}>`)
923
+ .join(', ')}); a key must be one of them, or “undefined” to keep the node’s own IRI.`);
924
+ }
925
+ return picked;
926
+ }
927
+ /** The {@link KeyField.pick} a type that declares none falls back to. */
928
+ function firstCandidate(candidates) {
929
+ return candidates[0];
930
+ }
753
931
  /**
754
932
  * The **IR Alias** predicate for a field: `urn:lde:‹SearchType.name›/‹field.name›`.
755
933
  * The extraction CONSTRUCT emits a field’s value under this minted predicate, and
@@ -848,11 +1026,18 @@ export function unixSecondsToIso(seconds) {
848
1026
  }
849
1027
  /**
850
1028
  * Derive the **indexed** physical engine field names a declaration produces:
851
- * the per-locale `search`/`sort` fanout. A localized `text` field’s display
852
- * fields are pattern-based and not enumerated here – see {@link displayFieldName}
853
- * and its siblings.
1029
+ * the per-locale `search`/`sort` fanout, and the field a `facet` reads. A
1030
+ * localized `text` field’s display fields are pattern-based and not enumerated
1031
+ * here – see {@link displayFieldName} and its siblings.
1032
+ *
1033
+ * The facet is the one member that depends on more than the declaration: a
1034
+ * facetable reference whose target declares a {@link FacetKeys facet policy}
1035
+ * facets a `${name}_facet` companion rather than itself, and only the `schema`
1036
+ * can resolve that target ({@link inheritedFacetKeys}). Without one the field
1037
+ * facets on its own name – the same reading the projection makes without a
1038
+ * schema, where it cannot re-key a reference either.
854
1039
  */
855
- export function physicalFields(field) {
1040
+ export function physicalFields(field, schema) {
856
1041
  if (field.kind === 'text') {
857
1042
  const locales = field.locales;
858
1043
  return {
@@ -862,10 +1047,40 @@ export function physicalFields(field) {
862
1047
  sort: field.sortable
863
1048
  ? locales.map((locale) => `${field.name}_sort_${locale}`)
864
1049
  : [],
1050
+ // Text is never facetable (`validateSearchType`).
1051
+ facet: undefined,
865
1052
  };
866
1053
  }
867
1054
  return {
868
1055
  search: field.searchable !== undefined ? [`${field.name}_search`] : [],
869
1056
  sort: [],
1057
+ facet: field.facetable !== true
1058
+ ? undefined
1059
+ : inheritedFacetKeys(field, schema) === undefined
1060
+ ? field.name
1061
+ : `${field.name}_facet`,
870
1062
  };
871
1063
  }
1064
+ /**
1065
+ * The {@link FacetKeys facet policy} a field inherits: the `facetKeys` of the
1066
+ * Root Type it names ({@link labelSourceNameOf}), when the field is a facetable
1067
+ * reference and the schema resolves that type. The boundary is *naming the
1068
+ * target* – the same line along which a reference is re-keyed and a join is
1069
+ * drawn – so a reference that names no type inherits nothing, whatever type
1070
+ * its values happen to point at; and a `derive`d reference, which produces its
1071
+ * own values rather than reading a referent, is re-keyed by nothing and so
1072
+ * narrowed by nothing either. The one reading the projection (which writes the
1073
+ * companion) and {@link physicalFields} (which names it) share.
1074
+ */
1075
+ export function inheritedFacetKeys(field, schema) {
1076
+ if (schema === undefined ||
1077
+ field.kind !== 'reference' ||
1078
+ field.facetable !== true ||
1079
+ field.derive !== undefined) {
1080
+ return undefined;
1081
+ }
1082
+ const targetName = labelSourceNameOf(field);
1083
+ return targetName === undefined
1084
+ ? undefined
1085
+ : rootTypeNamed(schema, targetName)?.facetKeys;
1086
+ }
@@ -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;AAmBhD;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,4BAA4B,CAC1C,IAAI,EAAE,MAAM,EACZ,MAAM,EAAE,MAAM,YAAY,GACzB,IAAI,CAuON"}
1
+ {"version":3,"file":"testing.d.ts","sourceRoot":"","sources":["../src/testing.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAoBhD;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,4BAA4B,CAC1C,IAAI,EAAE,MAAM,EACZ,MAAM,EAAE,MAAM,YAAY,GACzB,IAAI,CAuRN"}
package/dist/testing.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import { describe, expect, it } from 'vitest';
2
2
  import { filterOn, filterOperatorFor, } from './query.js';
3
- import { facetableFields, filterableFields, ID_FIELD, nestedReferenceType, outputFields, } from './schema.js';
3
+ import { facetableFields, filterableFields, ID_FIELD, inheritedFacetKeys, nestedReferenceType, outputFields, } from './schema.js';
4
4
  /**
5
5
  * The executable {@link SearchEngine} port contract (import from
6
6
  * `@lde/search/testing`): a vitest suite every engine adapter runs against a
@@ -200,6 +200,53 @@ export function describeSearchEngineContract(name, engine) {
200
200
  }
201
201
  }
202
202
  });
203
+ it('buckets a facet inheriting a policy on admitted keys only, labelled, while filtering stays exact', async () => {
204
+ // A facet policy (`RootType.facetKeys`) narrows the FACET and nothing
205
+ // else. Two things an adapter can get wrong, each silently: facet the
206
+ // companion field but report its buckets under the engine’s name, which
207
+ // loses every label and files the counts under a key no consumer asked
208
+ // for; and pick the filter operator off the declaration, so a `where`
209
+ // on an excluded value – still `facetable` in the schema, no longer a
210
+ // facet in the engine – partial-matches on a shared path prefix. The
211
+ // adapter’s fixture should carry two IRIs sharing a prefix so the
212
+ // second assertion bites.
213
+ for (const searchType of types()) {
214
+ for (const field of facetableFields(searchType)) {
215
+ const policy = inheritedFacetKeys(field, engine().schema);
216
+ if (policy === undefined) {
217
+ continue;
218
+ }
219
+ const result = await engine().search(searchType, {
220
+ ...browse(searchType),
221
+ limit: 20,
222
+ facets: [field.name],
223
+ });
224
+ const buckets = result.facets[field.name] ?? [];
225
+ for (const bucket of buckets) {
226
+ expect(policy.only(bucket.value)).toBe(true);
227
+ expect(bucket.label).toBeDefined();
228
+ }
229
+ if (field.output !== true || field.filterable !== true) {
230
+ continue; // Nothing to read the stored values back from.
231
+ }
232
+ const excluded = result.hits
233
+ .flatMap((hit) => referenceIds(hit.document[field.name]))
234
+ .find((id) => !policy.only(id));
235
+ if (excluded === undefined) {
236
+ continue;
237
+ }
238
+ const filtered = await engine().search(searchType, {
239
+ ...browse(searchType),
240
+ limit: 20,
241
+ where: [filterOn({ field: field.name, in: [excluded] })],
242
+ });
243
+ expect(filtered.total).toBeGreaterThan(0);
244
+ for (const hit of filtered.hits) {
245
+ expect(referenceIds(hit.document[field.name])).toContain(excluded);
246
+ }
247
+ }
248
+ }
249
+ });
203
250
  it('answers a browse query with hits, a total and facets', async () => {
204
251
  for (const searchType of types()) {
205
252
  const result = await engine().search(searchType, browse(searchType));
@@ -220,6 +267,16 @@ function mismatchedFilter(field, operator) {
220
267
  ? filterOn({ field, range: { min: 0 } })
221
268
  : filterOn({ field, in: ['x'] });
222
269
  }
270
+ /** The ids a reconstructed reference value carries – one labelled reference
271
+ * or a list of them – read the way a consumer reads them. */
272
+ function referenceIds(value) {
273
+ const references = Array.isArray(value) ? value : [value];
274
+ return references
275
+ .map((reference) => typeof reference === 'object' && reference !== null && 'id' in reference
276
+ ? String(reference.id)
277
+ : undefined)
278
+ .filter((id) => id !== undefined);
279
+ }
223
280
  /** The locale a query against this type may select (any is contract-valid). */
224
281
  function firstLocale(searchType) {
225
282
  for (const field of searchType.fields) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lde/search",
3
- "version": "0.23.0",
3
+ "version": "0.23.2",
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",