typesense-dotnet
typesense-dotnet copied to clipboard
.NET HTTP client for Typesense.
Typesense-dotnet
.net client for Typesense.
You can get the NuGet package here.
Feel free to make issues or create pull requests if you find any bugs or there are missing features.
Setup
Setup in service collection so it can be dependency injected. The AddTypesenseClient can be found in the Typesense.Setup namespace. Remember to change the settings to match your Typesense service. Right now you can specify multiple nodes, but the implementation has not been completed yet, so if you want to use this for multiple nodes, then put a load balancer in front of your services and point the settings to your load balancer.
If you are using externally hosted Typesense, like Typesense Cloud, it is recommended to enable HTTP compression to lower response times and reduce traffic.
var provider = new ServiceCollection()
.AddTypesenseClient(config =>
{
config.ApiKey = "mysecretapikey";
config.Nodes = new List<Node>
{
new Node("localhost", "8108", "http")
};
}, enableHttpCompression: false).BuildServiceProvider();
After that you can get it from the provider instance or dependency inject it.
var typesenseClient = provider.GetService<ITypesenseClient>();
Create collection
When you create the collection, you can specify each field with name, type and if it should be a facet, an optional or an indexed field.
var schema = new Schema(
"Addresses",
new List<Field>
{
new Field("id", FieldType.Int32, false),
new Field("houseNumber", FieldType.Int32, false),
new Field("accessAddress", FieldType.String, false, true),
new Field("metadataNotes", FieldType.String, false, true, false),
},
"houseNumber");
var createCollectionResult = await typesenseClient.CreateCollection(schema);
The example uses camelCase by default for field names, but you override this on the document you want to insert. Below is an example using snake_case.
public class Address
{
[JsonPropertyName("id")]
public string Id { get; set; }
[JsonPropertyName("house_number")]
public int HouseNumber { get; set; }
[JsonPropertyName("access_address")]
public string AccessAddress { get; set; }
[JsonPropertyName("metadata_notes")]
public string MetadataNotes { get; set; }
}
Update collection
Update existing collection.
var updateSchema = new UpdateSchema(new List<UpdateSchemaField>
{
// Example deleting existing field.
new UpdateSchemaField("metadataNotes", drop: true),
// Example adding a new field.
new UpdateSchemaField("city", FieldType.String, facet: false, optional: true)
});
var updateCollectionResponse = await typesenseClient.UpdateCollection("Addresses", updateSchema);
Index document
var address = new Address
{
Id = 1,
HouseNumber = 2,
AccessAddress = "Smedgade 25B"
};
var createDocumentResult = await typesenseClient.CreateDocument<Address>("Addresses", address);
Upsert document
var address = new Address
{
Id = 1,
HouseNumber = 2,
AccessAddress = "Smedgade 25B"
};
var upsertResult = await typesenseClient.UpsertDocument<Address>("Addresses", address);
Search document in collection
var query = new SearchParameters("Smed", "accessAddress");
var searchResult = await typesenseClient.Search<Address>("Addresses", query);
Search grouped
var query = new GroupedSearchParameters("Stark", "company_name", "country");
var response = await _client.SearchGrouped<Company>("companies", query);
Multi search documents
Multi search goes from one query to four, if you need more than that please open an issue and I'll implement more.
Example of using a single query in multi-search.
var query = new MultiSearchParameters("companies", "Stark", "company_name");
var response = await _client.MultiSearch<Company>(queryOne);
Example of using a two queries in multi-search.
var queryOne = new MultiSearchParameters("companies", "Stark", "company_name");
var queryTwo = new MultiSearchParameters("employees", "Kenny", "person_name");
var (r1, r2) = await _client.MultiSearch<Company, Employee>(queryOne, queryTwo);
Example of using a three queries in multi-search (you get the pattern currently it goes up to four.).
var queryOne = new MultiSearchParameters("companies", "Stark", "company_name");
var queryTwo = new MultiSearchParameters("employees", "Kenny", "person_name");
var queryThree = new MultiSearchParameters("companies", "Awesome Corp.", "company_name");
var (r1, r2, r3) = await _client.MultiSearch<Company, Employee, Company>(queryOne, queryTwo, queryThree);
Vector search
Typesense supports the ability to add embeddings generated by your Machine Learning models to each document, and then doing a nearest-neighbor search on them. This lets you build features like similarity search, recommendations, semantic search, visual search, etc. Here is an example of how to create a vector schema, a vector document, and perform a multi search using a vector as a query parameter.
const string COLLECTION_NAME = "address_vector_search";
var schema = new Schema(
COLLECTION_NAME,
new List<Field>
{
new Field("vec", FieldType.FloatArray, 4)
});
_ = await _client.CreateCollection(schema);
await _client.CreateDocument(
COLLECTION_NAME,
new AddressVectorSearch()
{
Id = "0",
Vec = new float[] { 0.04F, 0.234F, 0.113F, 0.001F }
});
var query = new MultiSearchParameters(COLLECTION_NAME, "*")
{
// vec:([0.96826, 0.94, 0.39557, 0.306488], k:100)
VectorQuery = new(
vector: new float[] { 0.96826F, 0.94F, 0.39557F, 0.306488F },
k: 100)
};
var response = await _client.MultiSearch<AddressVectorSearch>(query);
Retrieve a document on id
var retrievedDocument = await typesenseClient.RetrieveDocument<Address>("Addresses", "1");
Update document on id
var address = new Address
{
Id = 1,
HouseNumber = 2,
AccessAddress = "Smedgade 25B"
};
var updateDocumentResult = await typesenseClient.UpdateDocument<Address>("Addresses", "1", address);
Delete document on id
var deleteResult = await typesenseClient.DeleteDocument<Address>("Addresses", "1");
Update documents using filter
var updateResult = await typesenseClient.UpdateDocuments("Addresses", "houseNumber:=2", { "accessAddress": "Smedgade 25C" });
Delete documents using filter
var deleteResult = await typesenseClient.DeleteDocuments("Addresses", "houseNumber:>=3", 100);
Drop a collection on name
var deleteCollectionResult = await typesenseClient.DeleteCollection("Addresses");
Import documents
The default batch size is 40.
The default ImportType is Create.
You can pick between three different import types Create, Upsert, Update.
The returned values are a list of ImportResponse that contains a success code, error and the failed document as a string representation.
var importDocumentResults = await typesenseClient.ImportDocuments<Address>("Addresses", addresses, 40, ImportType.Create);
Export documents
var addresses = await typesenseClient.ExportDocuments<Address>("Addresses");
Api keys
Create key
ExpiresAt is optional.
Value is optional.
var apiKey = new Key(
"Example key one",
new[] { "*" },
new[] { "*" });
var createdKey = await typesenseClient.CreateKey(apiKey);
Retrieve key
var retrievedKey = await typesenseClient.RetrieveKey(0);
List keys
var keys = await typesenseClient.ListKeys();
Delete key
var deletedKey = await typesenseClient.DeleteKey(0);
Generate Scoped Search key
var scopedSearchKey = typesenseClient.GenerateScopedSearchKey("MainOrParentAPIKey", "{\"filter_by\":\"accessible_to_user_ids:2\"}");
Curation
While Typesense makes it really easy and intuitive to deliver great search results, sometimes you might want to promote certain documents over others. Or, you might want to exclude certain documents from a query's result set.
Using overrides, you can include or exclude specific documents for a given query.
Upsert
var searchOverride = new SearchOverride(new List<Include> { new Include("2", 1) }, new Rule("Sul", "exact"));
var upsertSearchOverrideResponse = await typesenseClient.UpsertSearchOverride("Addresses", "addresses-override", searchOverride);
List all overrides
var listSearchOverrides = await typesenseClient.ListSearchOverrides("Addresses");
Retrieve overrides
var retrieveSearchOverride = await typesenseClient.RetrieveSearchOverride("Addresses", "addresses-override");
Delete override
var deletedSearchOverrideResult = await typesenseClient.DeleteSearchOverride("Addresses", "addresses-override");
Collection alias
An alias is a virtual collection name that points to a real collection. Read more here.
Upsert collection alias
var upsertCollectionAlias = await typesenseClient.UpsertCollectionAlias("Address_Alias", new CollectionAlias("Addresses"));
List all collection aliases
var listCollectionAliases = await typesenseClient.ListCollectionAliases();
Retrieve collection alias
var retrieveCollectionAlias = await typesenseClient.RetrieveCollectionAlias("Address_Alias");
Delete collection alias
var deleteCollectionAlias = await typesenseClient.DeleteCollectionAlias("Addresses_Alias");
Synonyms
The synonyms feature allows you to define search terms that should be considered equivalent. For eg: when you define a synonym for sneaker as shoe, searching for sneaker will now return all records with the word shoe in them, in addition to records with the word sneaker. Read more here.
Upsert synonym
var upsertSynonym = await typesenseClient.UpsertSynonym("Addresses", "Address_Synonym", new SynonymSchema(new List<string> { "Sultan", "Soltan", "Softan" }));
Retrieve a synonym
var retrieveSynonym = await typesenseClient.RetrieveSynonym("Addresses", "Address_Synonym");
List all synonyms
var listSynonyms = await typesenseClient.ListSynonyms("Addresses");
Delete synonym
var deleteSynonym = await typesenseClient.DeleteSynonym("Addresses", "Address_Synonym");
Metrics
Get current RAM, CPU, Disk & Network usage metrics.
var metrics = await typesenseClient.RetrieveMetrics();
Stats
Get stats about API endpoints.
var stats = await typesenseClient.RetrieveStats();
Health
Get stats about API endpoints.
var health = await typesenseClient.RetrieveHealth();
Snapshot
Asynchronously initiates a snapshot operation on the Typesense server.
var snapshotResponse = await typesenseClient.CreateSnapshot("/my_snapshot_path");
Disk Compaction
Asynchronously initiates the running of a compaction of the underlying RocksDB database.
var diskCompactionResponse = await typesenseClient.CompactDisk();
Typesense API Errors
Typesense API exceptions in the Typesense-api-errors spec.
| Type | Description |
|---|---|
TypesenseApiException |
Base exception type for Typesense api exceptions. |
TypesenseApiBadRequestException |
Bad Request - The request could not be understood due to malformed syntax. |
TypesenseApiUnauthorizedException |
Unauthorized - Your API key is wrong. |
TypesenseApiNotFoundException |
Not Found - The requested resource is not found. |
TypesenseApiConflictException |
Conflict - When a resource already exists. |
TypesenseApiUnprocessableEntityException |
Unprocessable Entity - Request is well-formed, but cannot be processed. |
TypesenseApiServiceUnavailableException |
Service Unavailable - We’re temporarily offline. Please try again later. |
Tests
Running all tests.
dotnet test
Running only unit tests
dotnet test --filter Category=Unit
Running integration tests
dotnet test --filter Category=Integration
To run integration tests, you can execute Typesense in a docker container by using the command below. The process utilizes the /tmp/data folder however, if you prefer to place it in a different directory, you can modify the path accordingly.
docker run -p 8108:8108 -v /tmp/data:/data typesense/typesense:0.25.1 --data-dir /data --api-key=key