Everything in this client is typed except the one thing a caller actually gets wrong. A filter names
"lang", a payload index names "lang", an upsert writes "lang", and a search written a month later
asks for "language". It compiles, the request is well formed, Qdrant accepts it, and it matches
nothing. That is precisely the failure matchPhrase had before 2.2.0, and payload keys are the last
surface in this client where it is still reachable.
Kotlin can remove it, and this is the kind of thing a Kotlin client can do that a client generated from
a protobuf definition cannot. A payload declared as a data class describes what a collection's points
carry; a KSP processor generates from it the keys, the payload index declarations and the typed filter
entries, so a renamed property fails the build at every call site that used it and the index declaration
moves with the code that depends on it.
Three constraints keep this from turning into a framework.
It is opt-in and the string API stays public. Qdrant's payloads are schemaless, storing a payload nobody
declared is a legitimate thing to do, and a typed surface that only works when everything is declared
would make the untyped path feel like a mistake.
It generates keys, not a repository. An object mapper over a vector database is a second product with a
second set of opinions about ids, embeddings and lifecycles, and this project has spent five tiers not
being that.
And it has to work in a multiplatform build for the targets kdrant-core publishes. That is the part to
settle before writing anything else, because a processor that only runs for the JVM hands the typed
surface to exactly the callers who were least likely to lose a key.
Exit criterion: a payload declared as a Kotlin data class produces the payload keys, the index
declarations and the filter entries a search uses; renaming a property fails the build at the call sites
that used it; and the generated code compiles for every target kdrant-core publishes for.
Everything in this client is typed except the one thing a caller actually gets wrong. A filter names
"lang", a payload index names"lang", an upsert writes"lang", and a search written a month laterasks for
"language". It compiles, the request is well formed, Qdrant accepts it, and it matchesnothing. That is precisely the failure
matchPhrasehad before2.2.0, and payload keys are the lastsurface in this client where it is still reachable.
Kotlin can remove it, and this is the kind of thing a Kotlin client can do that a client generated from
a protobuf definition cannot. A payload declared as a data class describes what a collection's points
carry; a KSP processor generates from it the keys, the payload index declarations and the typed filter
entries, so a renamed property fails the build at every call site that used it and the index declaration
moves with the code that depends on it.
Three constraints keep this from turning into a framework.
It is opt-in and the string API stays public. Qdrant's payloads are schemaless, storing a payload nobody
declared is a legitimate thing to do, and a typed surface that only works when everything is declared
would make the untyped path feel like a mistake.
It generates keys, not a repository. An object mapper over a vector database is a second product with a
second set of opinions about ids, embeddings and lifecycles, and this project has spent five tiers not
being that.
And it has to work in a multiplatform build for the targets
kdrant-corepublishes. That is the part tosettle before writing anything else, because a processor that only runs for the JVM hands the typed
surface to exactly the callers who were least likely to lose a key.
Exit criterion: a payload declared as a Kotlin data class produces the payload keys, the index
declarations and the filter entries a search uses; renaming a property fails the build at the call sites
that used it; and the generated code compiles for every target
kdrant-corepublishes for.