Problem
The physical fanout mints engine field names from a declared field’s name by suffix (physicalFields, displayFieldName): ${name}_search, ${name}_search_<locale>, ${name}_sort_<locale>, the display pattern ${name}_<lang>, and – since the facet policy – the companion ${name}_facet. Nothing stops a schema from declaring a field whose name equals one of these. validateSearchType restricts the name pattern and reserves only id; the companion names are not reserved.
A collision passes validation and breaks silently, wherever the duplicate meets the engine:
- Collection definition – a type with a policy-inheriting reference
about and a declared keyword about_facet emits two Typesense fields named about_facet (one string[], one string). Typesense rejects the create, or keeps one declaration and then fails imports carrying the other shape.
- Response boundary – the engine maps
facet_counts[].field_name back to declared names through a map keyed by engine name (declaredFacetNames in search.ts). The declared field’s identity entry about_facet → about_facet is overwritten by the companion’s about_facet → about, so a facet on the declared field is filed under the wrong key and labelled from the wrong source.
- Pre-existing class – the same hole already exists for the older companions: a keyword
title_search beside a searchable title, a keyword label_nl beside an output text field label (the display pattern is per-language, so that collision is data-dependent).
Surfaced by the review of #765, which added a second companion suffix without adding a check – see “Known, pre-existing hole, not widened here” in its description. Left out there deliberately: it is a schema-wide naming rule, not part of the facet policy.
Options
- Reserve the suffix shapes outright: reject any declared name that another declared field could fan out to, regardless of whether that field currently produces the companion (i.e. ignore
searchable/sortable/policy state). Simple, predictable, needs no schema – but rejects names that are harmless today and couples the rule to flags a later edit may flip anyway, which is rather the point.
- Reject actual collisions only: per type, a declared name may not equal a sibling’s produced companion (
_search, _search_<locale>, _sort_<locale>, display _<lang> for its declared locales). The _facet companion exists only when the named target declares facetKeys, so that half is a schema-level check in searchSchema (where the label-source resolvability check already lives). Precise, but the answer changes when a flag or a policy changes.
Leaning towards (1) restricted to collision with a sibling’s name: for every pair of declared fields, reject b.name === a.name + '_' + suffix… shapes without consulting flags – stable under flag changes, still never rejects a name with no sibling to collide with.
Either way this is a new validation failure for schemas that currently declare such names, acceptable pre-1.0.
Problem
The physical fanout mints engine field names from a declared field’s name by suffix (
physicalFields,displayFieldName):${name}_search,${name}_search_<locale>,${name}_sort_<locale>, the display pattern${name}_<lang>, and – since the facet policy – the companion${name}_facet. Nothing stops a schema from declaring a field whose name equals one of these.validateSearchTyperestricts the name pattern and reserves onlyid; the companion names are not reserved.A collision passes validation and breaks silently, wherever the duplicate meets the engine:
aboutand a declared keywordabout_facetemits two Typesense fields namedabout_facet(onestring[], onestring). Typesense rejects the create, or keeps one declaration and then fails imports carrying the other shape.facet_counts[].field_nameback to declared names through a map keyed by engine name (declaredFacetNamesinsearch.ts). The declared field’s identity entryabout_facet → about_facetis overwritten by the companion’sabout_facet → about, so a facet on the declared field is filed under the wrong key and labelled from the wrong source.title_searchbeside a searchabletitle, a keywordlabel_nlbeside an output text fieldlabel(the display pattern is per-language, so that collision is data-dependent).Surfaced by the review of #765, which added a second companion suffix without adding a check – see “Known, pre-existing hole, not widened here” in its description. Left out there deliberately: it is a schema-wide naming rule, not part of the facet policy.
Options
searchable/sortable/policy state). Simple, predictable, needs no schema – but rejects names that are harmless today and couples the rule to flags a later edit may flip anyway, which is rather the point._search,_search_<locale>,_sort_<locale>, display_<lang>for its declared locales). The_facetcompanion exists only when the named target declaresfacetKeys, so that half is a schema-level check insearchSchema(where the label-source resolvability check already lives). Precise, but the answer changes when a flag or a policy changes.Leaning towards (1) restricted to collision with a sibling’s name: for every pair of declared fields, reject
b.name === a.name + '_' + suffix…shapes without consulting flags – stable under flag changes, still never rejects a name with no sibling to collide with.Either way this is a new validation failure for schemas that currently declare such names, acceptable pre-1.0.