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.
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"),
],
...Import Typesense onto your Swift Project:
import TypesenseDeclare 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))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).
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.
let jsonL = Data("{}".utf8)
let (data, response) = try await client.collection(name: "companies").documents().importBatch(jsonL, options: ImportDocumentsParameters(
batchSize: 10,
returnId: false,
remoteEmbeddingBatchSize: 10,
returnDoc: true,
action: .upsert,
dirtyValues: .drop
))let (data, response) = try await client.collection(name: "companies").documents().update(
document: ["company_size": "large"],
options: UpdateDocumentsParameters(filterBy: "num_employees:>1000")
)let (data, response) = try await client.collection(name: "companies").documents().delete(
options: DeleteDocumentsParameters(filterBy: "num_employees:>100")
)let (data, response) = try await client.collection(name: "companies").documents().export(options: ExportDocumentsParameters(excludeFields: "country"))let schema = CollectionAliasSchema(collectionName: "companies_june")
let (data, response) = try await client.aliases().upsert(name: "companies", collection: schema)let (data, response) = try await client.aliases().retrieve()let (data, response) = try await client.aliases().retrieve(name: "companies")let (data, response) = try await client.aliases().delete(name: "companies")let adminKey = ApiKeySchema(description: "Test key with all privileges", actions: ["*"], collections: ["*"])
let (data, response) = try await client.keys().create(adminKey)let (data, response) = try await client.keys().retrieve()let (data, response) = try await client.keys().retrieve(id: 1)let (data, response) = try await client.keys().delete(id: 1)let schema = ConversationModelCreateSchema(
modelName: "openai/gpt-3.5-turbo",
historyCollection: "conversation_store",
maxBytes: 16384,
id: "conv-model-1",
apiKey: "OPENAI_API_KEY",
systemPrompt: "You are an assistant for question-answering...",
ttl: 10000,
)
let (data, response) = try await client.conversations().models().create(params: schema)let (data, response) = try await client.conversations().models().retrieve()let (data, response) = try await client.conversations().model(modelId: "conv-model-1").retrieve()let (data, response) = try await client.conversations().model(modelId: "conv-model-1").update(params: ConversationModelUpdateSchema(
systemPrompt: "..."
))let (data, response) = try await client.conversations().model(modelId: "conv-model-1").delete()let schema = CurationSetCreateSchema(items: [
CurationItemCreateSchema(
rule: CurationRule( query: "apple", match: .exact),
includes: [
CurationInclude(id: "422", position: 1),
CurationInclude(id: "54", position: 2),
], excludes: [CurationExclude(id: "287")],
id: "customize-apple"
)
])
let (data, response) = try await client.curationSets().upsert("curate_products", schema)let (data, response) = try await client.curationSets().retrieve()let (data, response) = try await client.curationSet("curate_products").retrieve()let (data, response) = try await client.curationSet("curate_products").delete()let (data, response) = try await client.curationSet("curate_products").items().retrieve()let (data, response) = try await client.curationSet("curate_products").items().upsert("customize-apple-2", CurationItemCreateSchema(
rule: CurationRule( query: "apple", match: .exact),
includes: [
CurationInclude(id: "422", position: 1),
CurationInclude(id: "54", position: 2),
], excludes: [CurationExclude(id: "287")],
))let (data, response) = try await client.curationSet("curate_products").item("customize-apple").retrieve()let (data, response) = try await client.curationSet("curate_products").item("customize-apple").delete()let schema = PresetUpsertSchema(
value: PresetUpsertSchemaValue.typeSearchParameters(SearchParameters(q: "apple"))
// or: value: PresetUpsertSchemaValue.typeMultiSearchSearchesParameter(MultiSearchSearchesParameter(searches: [MultiSearchCollectionParameters(q: "apple")]))
)
let (data, response) = try await client.presets().upsert(presetName: "listing_view", params: schema)let (data, response) = try await client.presets().retrieve()let (data, response) = try await client.preset("listing_view").retrieve()
switch data?.value {
case .typeSearchParameters(let value):
print(value)
case .typeMultiSearchSearchesParameter(let value):
print(value)
}let (data, response) = try await client.preset("listing_view").delete()let schema = StopwordsSetUpsertSchema(
stopwords: ["states","united"],
locale: "en"
)
let (data, response) = try await client.stopwords().upsert(stopwordsSetId: "stopword_set1", params: schema)let (data, response) = try await client.stopwords().retrieve()let (data, response) = try await client.stopword("stopword_set1").retrieve()let (data, response) = try await client.stopword("stopword_set1").delete()let schema = SynonymSetCreateSchema(items: [
SynonymItemSchema(synonyms: ["blazer", "coat", "jacket"], id:"coat-synonyms", root: "outerwear")
])
let (data, response) = try await utilClient.synonymSets().upsert("clothing-synonyms", schema)let (data, response) = try await client.synonymSets().retrieve()let (data, response) = try await client.synonymSet("clothing-synonyms").retrieve()let (data, response) = try await client.synonymSet("clothing-synonyms").delete()let schema = SynonymItemUpsertSchema(synonyms: ["blazer", "coat", "jacket"], root: "outerwear")
let (data, response) = try await client.synonymSet("clothing-synonyms").items().upsert("coat-synonyms", schema)let (data, response) = try await client.synonymSet("clothing-synonyms").items().retrieve()let (data, response) = try await client.synonymSet("clothing-synonyms").item("coat-synonyms").retrieve()let (data, response) = try await client.synonymSet("clothing-synonyms").item("coat-synonyms").delete()let (data, response) = try await client.operations().getDebug()let (data, response) = try await client.operations().getHealth()let (data, response) = try await client.operations().getStats()let (data, response) = try await client.operations().getMetrics()let (data, response) = try await client.operations().vote()let (data, response) = try await client.operations().toggleSlowRequestLog(seconds: 2)let (data, response) = try await client.operations().clearCache()let (data, response) = try await client.operations().snapshot(path: "/tmp/typesense-data-snapshot")Issues and pull requests are welcome on GitHub at Typesense Swift. Do note that the Models used in the Swift client are generated by OpenAPI Generator.
When updating or adding new parameters and endpoints, make changes directly in the Typesense API spec repository.
Once your changes are merged, you can update this project as follows:
swift run Tasks fetch
swift run Tasks preprocess
swift run Tasks code-genThis will:
- Download the latest API spec.
- Write it to our local
openapi.yml. - Preprocess it into
preprocessed_openapi.yml. - Generate and replace the
Sources/Typesense/Modelsfolder.
The preprocessing step does two things:
- Flatten the URL params defined as objects into individual URL parameters (in
PreprocessOpenAPI.swift) - Inject OpenAPI vendor attributes
x-swift-*(e.g., generic parameters, schema builders) into the spec before code generation (inAddVendorAttributes.swift)
- Scoped Search Key