CompositeSegmentObserver
Feeds several observers from the one StoreOptions.segmentObserver slot.
A store that wants both a SchemaCatalog and an IndexCatalog needs two observers and core offers one place to put them. Composing here rather than making the option a list is deliberate: the core wraps whatever it is given in a guard that abandons an observation when a callback throws, and that guard is per observer on purpose — a broken catalog must not cost a document, and by exactly the same argument a broken catalog must not cost the index its segment either. A list inside core would put that isolation policy in the one module that has no business deciding it.
So the composite is the isolation. A child that throws is dropped for that segment, its own observerFailed is told, and every other child carries on. Nothing propagates out of observe or complete, which is what the core's guard would otherwise see.
val catalog = SchemaCatalog(directory)
val indexes = IndexCatalog(directory)
DocumentStore.open(
directory,
StoreOptions(segmentObserver = CompositeSegmentObserver(listOf(catalog, indexes))),
).use { store ->
catalog.attach(store)
indexes.attach(store)
// …
}Two attachments mean two full scans of anything neither covers. Nothing here can avoid that: DocumentStore.backfill takes one observer and each layer decides for itself which segments it needs. The single co-attaching pass is Rabosh in rabosh-api, which attaches both layers with backfill = false and then runs one backfill through a composite — so the composition above is what makes that pass possible rather than merely tidy, and a caller who does not want the facade can do the same three lines by hand.
Functions
Reached only when a child's own SegmentObserver.observerFailed threw.