Skip to content

typesense/typesense-swift

Repository files navigation

Typesense Swift

A great new way to implement your searches on iOS using Typesense βš‘οΈπŸ”βœ¨ Typesense Swift is a high level wrapper that helps you easily implement searching using Typesense.

Installation

Add Typesense Swift Swift Package to your project. You can refer Apple's Documentation to add Typesense Swift as a dependency to your iOS Project. You can also import Typesense into your own Swift Package by adding this line to dependencies array of Package.swift:

...
dependencies: [
           .package(url: "https://github.com/typesense/typesense-swift", .upToNextMajor(from: "1.0.0"),
],
...

Usage

Setting up the client

Import Typesense onto your Swift Project:

import Typesense

Declare the Typesense nodes that are available as Node members:

let node1 = Node(url: "http://localhost:8108") // or
let node2 = Node(host: "xxx-1.a1.typesense.net", port: "443", nodeProtocol: "https")

Create a configuration and hence a client with the Nodes mentioned:

let myConfig = Configuration(nodes: [node1, node2], apiKey: "coolstuff")

let client = Client(config: myConfig)

You can use Typesense parameters like nearestNode and connectionTimeoutSeconds while creating the configuration. You can also pass in a logger parameter to debug the code like this:

let myConfig = Configuration(nodes: [node1, node2], apiKey: "coolstuff", logger: Logger(debugMode: true))

Indexing documents

You can create a collection by first defining a collection schema:

let myCoolSchema = CollectionSchema(name: "schools", fields: [Field(name: "school_name", type: "string"), Field(name: "num_students", type: "int32"), Field(name: "country", type: "string", facet: true)], defaultSortingField: "num_students")

let (data, response) = try await client.collections.create(schema: myCoolSchema)

Define the structure of your document as per your collection, and index it by inserting/upserting it to the collection:

struct School: Codable {
    var id: String
    var school_name: String
    var num_students: Int
    var country: String
}

let document = School(id: "7", school_name: "Hogwarts", num_students: 600, country: "United Kingdom")
let documentData = try JSONEncoder().encode(document)
let (data, response) = try await client.collection(name: "schools").documents().create(document: documentData)
//or
let (data, response) = try await client.collection(name: "schools").documents().upsert(document: documentData)

You can perform CRUD actions to Collections and Documents that belong to a certain collection. You can also use .importBatch() on the documents() method to import and index a batch of documents (in .jsonl format).

Searching

Define your search parameters clearly and then perform the search operation by mentioning your Document Type:

let searchParameters = SearchParameters(q: "hog", queryBy: "school_name", filterBy: "num_students:>500", sortBy: "num_students:desc")

let (data, response) = try await client.collection(name: "schools").documents().search(searchParameters, for: School.self)

This returns a SearchResult object as the data, which can be further parsed as desired.

Bulk import documents

let jsonL = Data("{}".utf8)
let (data, response) = try await client.collection(name: "companies").documents().importBatch(jsonL, options: ImportDocumentsParameters(
    action: .upsert,
    batchSize: 10,
    dirtyValues: .drop,
    remoteEmbeddingBatchSize: 10,
    returnDoc: true,
    returnId: false
))

Update multiple documents by query

let (data, response) = try await client.collection(name: "companies").documents().update(
    document: ["company_size": "large"],
    options: UpdateDocumentsByFilterParameters(filterBy: "num_employees:>1000")
)

Delete multiple documents by query

let (data, response) = try await client.collection(name: "companies").documents().delete(
    options: DeleteDocumentsParameters(filterBy: "num_employees:>100")
)

Export documents

let (data, response) = try await client.collection(name: "companies").documents().export(options: ExportDocumentsParameters(excludeFields: "country"))

Create or update a collection alias

let schema = CollectionAliasSchema(collectionName: "companies_june")
let (data, response) = try await client.aliases().upsert(name: "companies", collection: schema)

Retrieve all aliases

let (data, response) = try await client.aliases().retrieve()

Retrieve an alias

let (data, response) = try await client.aliases().retrieve(name: "companies")

Delete an alias

let (data, response) = try await client.aliases().delete(name: "companies")

Create an API key

let adminKey = ApiKeySchema(_description: "Test key with all privileges", actions: ["*"], collections: ["*"])
let (data, response) = try await client.keys().create(adminKey)

Retrieve all API keys

let (data, response) = try await client.keys().retrieve()

Retrieve an API key

let (data, response) = try await client.keys().retrieve(id: 1)

Delete an API key

let (data, response) = try await client.keys().delete(id: 1)

Create a conversation model

let schema = ConversationModelCreateSchema(
    _id: "conv-model-1",
    modelName: "openai/gpt-3.5-turbo",
    apiKey: "OPENAI_API_KEY",
    historyCollection: "conversation_store",
    systemPrompt: "You are an assistant for question-answering...",
    ttl: 10000,
    maxBytes: 16384
)
let (data, response) = try await client.conversations().models().create(params: schema)

Retrieve all conversation models

let (data, response) = try await client.conversations().models().retrieve()

Retrieve a conversation model

let (data, response) = try await client.conversations().model(modelId: "conv-model-1").retrieve()

Update a conversation model

let (data, response) = try await client.conversations().model(modelId: "conv-model-1").update(params: ConversationModelUpdateSchema(
    systemPrompt: "..."
))

Delete a conversation model

let (data, response) = try await client.conversations().model(modelId: "conv-model-1").delete()

Create or update an override

let schema = SearchOverrideSchema<MetadataType>(
    rule: SearchOverrideRule(tags: ["test"], query: "apple", match: SearchOverrideRule.Match.exact, filterBy: "employees:=50"),
    includes: [SearchOverrideInclude(_id: "include-id", position: 1)],
    excludes: [SearchOverrideExclude(_id: "exclude-id")],
    filterBy: "test:=true",
    removeMatchedTokens: false,
    metadata: MetadataType(message: "test-json"),
    sortBy: "num_employees:desc",
    replaceQuery: "test",
    filterCuratedHits: false,
    effectiveFromTs: 123,
    effectiveToTs: 456,
    stopProcessing: false
)
let (data, response) = try await client.collection(name: "books").overrides().upsert(overrideId: "test-id", params: schema)

Retrieve all overrides

let (data, response) = try await client.collection(name: "books").overrides().retrieve(metadataType: Never.self)

Retrieve an override

let (data, response) = try await client.collection(name: "books").override("test-id").retrieve(metadataType: MetadataType.self)

Delete an override

let (data, response) = try await client.collection(name: "books").override("test-id").delete()

Create or update a preset

let schema = PresetUpsertSchema(
    value: PresetValue.singleCollectionSearch(SearchParameters(q: "apple"))
    // or: value: PresetValue.multiSearch(MultiSearchSearchesParameter(searches: [MultiSearchCollectionParameters(q: "apple")]))
)
let (data, response) = try await client.presets().upsert(presetName: "listing_view", params: schema)

Retrieve all presets

let (data, response) = try await client.presets().retrieve()

Retrieve a preset

let (data, response) = try await client.preset("listing_view").retrieve()

switch data?.value {
    case .singleCollectionSearch(let value):
        print(value)
    case .multiSearch(let value):
        print(value)
}

Delete a preset

let (data, response) = try await client.preset("listing_view").delete()

Create or update a stopwords set

let schema = StopwordsSetUpsertSchema(
    stopwords: ["states","united"],
    locale: "en"
)
let (data, response) = try await client.stopwords().upsert(stopwordsSetId: "stopword_set1", params: schema)

Retrieve all stopwords sets

let (data, response) = try await client.stopwords().retrieve()

Retrieve a stopwords set

let (data, response) = try await client.stopword("stopword_set1").retrieve()

Delete a stopwords set

let (data, response) = try await client.stopword("stopword_set1").delete()

Create or update a synonym

let schema = SearchSynonymSchema(synonyms: ["blazer", "coat", "jacket"])
let (data, response) = try await client.collection(name: "products").synonyms().upsert(id: "coat-synonyms", schema)

Retrieve all synonyms

let (data, response) = try await client.collection(name: "products").synonyms().retrieve()

Retrieve a synonym

let (data, response) = try await client.collection(name: "products").synonyms().retrieve(id: "coat-synonyms")

Delete a synonym

let (data, response) = try await myClient.collection(name: "products").synonyms().delete(id: "coat-synonyms")

Retrieve debug information

let (data, response) = try await client.operations().getDebug()

Retrieve health status

let (data, response) = try await client.operations().getHealth()

Retrieve API stats

let (data, response) = try await client.operations().getStats()

Retrieve Cluster Metrics

let (data, response) = try await client.operations().getMetrics()

Re-elect Leader

let (data, response) = try await client.operations().vote()

Toggle Slow Request Log

let (data, response) = try await client.operations().toggleSlowRequestLog(seconds: 2)

Clear cache

let (data, response) = try await client.operations().clearCache()

Create Snapshot (for backups)

let (data, response) = try await client.operations().snapshot(path: "/tmp/typesense-data-snapshot")

Contributing

Issues and pull requests are welcome on GitHub at Typesense Swift. Do note that the Models used in the Swift client are generated by Swagger-Codegen and are automated to be modified in order to prevent major errors. So please do use the shell script that is provided in the repo to generate the models:

sh get-models.sh

The generated Models (inside the Models directory) are to be used inside the Models directory of the source code as well. Models need to be generated as and when the Typesense-Api-Spec is updated.

TODO: Features

  • Scoped Search Key