Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import PackageDescription
let package = Package(
name: "Typesense",
platforms: [
.iOS(.v15), .macOS(.v12)
.iOS(.v13), .macOS(.v12)
],
products: [
// Products define the executables and libraries a package produces, and make them visible to other packages.
Expand Down
13 changes: 13 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -92,3 +92,16 @@ 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](https://github.com/typesense/typesense-api-spec) is updated.

## TODO: Features

- Curation API
- Multisearch
- Dealing with Dirty Data
- Scoped Search Key

## TODO: Testing

- Geosearch
- Auto schema detection
- Importing a .jsonl file
62 changes: 62 additions & 0 deletions Sources/Typesense/Alias.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import Foundation

public struct Alias {
var apiCall: ApiCall
let RESOURCEPATH = "aliases"
var config: Configuration

public init(config: Configuration) {
apiCall = ApiCall(config: config)
self.config = config
}

public func upsert(name: String, collection: CollectionAliasSchema) async throws -> (CollectionAlias?, URLResponse?) {
let schemaData: Data?

schemaData = try encoder.encode(collection)

if let validSchema = schemaData {
let (data, response) = try await apiCall.put(endPoint: "\(RESOURCEPATH)/\(name)", body: validSchema)
if let result = data {
let alias = try decoder.decode(CollectionAlias.self, from: result)
return (alias, response)
}
}
return (nil, nil)
}

public func retrieve(name: String) async throws -> (CollectionAlias?, URLResponse?) {
let (data, response) = try await apiCall.get(endPoint: "\(RESOURCEPATH)/\(name)")
if let result = data {
if let notExists = try? decoder.decode(ApiResponse.self, from: result) {
throw ResponseError.aliasNotFound(desc: "Alias \(notExists.message)")
}
let alias = try decoder.decode(CollectionAlias.self, from: result)
return (alias, response)
}
return (nil, nil)
}

public func retrieve() async throws -> (CollectionAliasesResponse?, URLResponse?) {
let (data, response) = try await apiCall.get(endPoint: "\(RESOURCEPATH)")
if let result = data {
let aliases = try decoder.decode(CollectionAliasesResponse.self, from: result)
return (aliases, response)
}
return (nil, nil)
}

public func delete(name: String) async throws -> (CollectionAlias?, URLResponse?) {
let (data, response) = try await apiCall.delete(endPoint: "\(RESOURCEPATH)/\(name)")
if let result = data {
if let notExists = try? decoder.decode(ApiResponse.self, from: result) {
throw ResponseError.aliasNotFound(desc: "Alias \(notExists.message)")
}
let alias = try decoder.decode(CollectionAlias.self, from: result)
return (alias, response)
}
return (nil, nil)
}


}
62 changes: 62 additions & 0 deletions Sources/Typesense/ApiKeys.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import Foundation

public struct ApiKeys {
var apiCall: ApiCall
let RESOURCEPATH = "keys"

public init(config: Configuration) {
apiCall = ApiCall(config: config)
}

public func create(_ keySchema: ApiKeySchema) async throws -> (ApiKey?, URLResponse?) {
var schemaData: Data? = nil

schemaData = try encoder.encode(keySchema)

if let validSchema = schemaData {
let (data, response) = try await apiCall.post(endPoint: "\(RESOURCEPATH)", body: validSchema)
if let result = data {
let keyResponse = try decoder.decode(ApiKey.self, from: result)
return (keyResponse, response)
}
}

return (nil, nil)
}

public func retrieve(id: Int) async throws -> (ApiKey?, URLResponse?) {

let (data, response) = try await apiCall.get(endPoint: "\(RESOURCEPATH)/\(id)")
if let result = data {
if let notFound = try? decoder.decode(ApiResponse.self, from: result) {
throw ResponseError.apiKeyNotFound(desc: notFound.message)
}
let keyResponse = try decoder.decode(ApiKey.self, from: result)
return (keyResponse, response)
}

return (nil, nil)
}

public func retrieve() async throws -> (ApiKeysResponse?, URLResponse?) {

let (data, response) = try await apiCall.get(endPoint: "\(RESOURCEPATH)")
if let result = data {
let keyResponse = try decoder.decode(ApiKeysResponse.self, from: result)
return (keyResponse, response)
}

return (nil, nil)
}

public func delete(id: Int) async throws -> (Data?, URLResponse?) {

let (data, response) = try await apiCall.delete(endPoint: "\(RESOURCEPATH)/\(id)")
if let result = data {
if let notFound = try? decoder.decode(ApiResponse.self, from: result) {
throw ResponseError.apiKeyNotFound(desc: notFound.message)
}
}
return (data, response)
}
}
12 changes: 12 additions & 0 deletions Sources/Typesense/Client.swift
Original file line number Diff line number Diff line change
Expand Up @@ -15,4 +15,16 @@ public struct Client {
public func collection(name: String) -> Collection {
return Collection(config: self.configuration, collectionName: name)
}

public func keys() -> ApiKeys {
return ApiKeys(config: self.configuration)
}

public func aliases() -> Alias {
return Alias(config: self.configuration)
}

public func operations() -> Operations {
return Operations(config: self.configuration)
}
}
4 changes: 4 additions & 0 deletions Sources/Typesense/Collection.swift
Original file line number Diff line number Diff line change
Expand Up @@ -44,4 +44,8 @@ public struct Collection {
}
return (nil, response)
}

public func synonyms() -> Synonyms {
return Synonyms(config: self.config, collectionName: self.collectionName)
}
}
24 changes: 24 additions & 0 deletions Sources/Typesense/Document.swift
Original file line number Diff line number Diff line change
Expand Up @@ -15,16 +15,40 @@ public struct Document {

public func delete() async throws -> (Data?, URLResponse?) {
let (data, response) = try await apiCall.delete(endPoint: "\(RESOURCEPATH)/\(self.id)")
if let result = data {
if let responseErr = try? decoder.decode(ApiResponse.self, from: result) {
if (responseErr.message == "Not Found") {
throw ResponseError.invalidCollection(desc: "Collection \(self.collectionName) \(responseErr.message)")
}
throw ResponseError.documentDoesNotExist(desc: responseErr.message)
}
}
return (data, response)
}

public func retrieve() async throws -> (Data?, URLResponse?) {
let (data, response) = try await apiCall.get(endPoint: "\(RESOURCEPATH)/\(self.id)")
if let result = data {
if let responseErr = try? decoder.decode(ApiResponse.self, from: result) {
if (responseErr.message == "Not Found") {
throw ResponseError.invalidCollection(desc: "Collection \(self.collectionName) \(responseErr.message)")
}
throw ResponseError.documentDoesNotExist(desc: responseErr.message)
}
}
return (data, response)
}

public func update(newDocument: Data) async throws -> (Data?, URLResponse?) {
let (data, response) = try await apiCall.patch(endPoint: "\(RESOURCEPATH)/\(self.id)", body: newDocument)
if let result = data {
if let responseErr = try? decoder.decode(ApiResponse.self, from: result) {
if (responseErr.message == "Not Found") {
throw ResponseError.invalidCollection(desc: "Collection \(self.collectionName) \(responseErr.message)")
}
throw ResponseError.documentDoesNotExist(desc: responseErr.message)
}
}
return (data, response)
}

Expand Down
36 changes: 35 additions & 1 deletion Sources/Typesense/Documents.swift
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import Foundation

public struct Documents {
var apiCall: ApiCall
var collectionName: String?
var collectionName: String
let RESOURCEPATH: String

public init(config: Configuration, collectionName: String) {
Expand All @@ -13,13 +13,42 @@ public struct Documents {

public func create(document: Data) async throws -> (Data?, URLResponse?) {
let (data, response) = try await apiCall.post(endPoint: RESOURCEPATH, body: document)
if let result = data {
if let responseErr = try? decoder.decode(ApiResponse.self, from: result) {
if (responseErr.message == "Not Found") {
throw ResponseError.invalidCollection(desc: "Collection \(self.collectionName) \(responseErr.message)")
}
throw ResponseError.documentAlreadyExists(desc: responseErr.message)
}
}
return (data, response)
}

public func upsert(document: Data) async throws -> (Data?, URLResponse?) {
let upsertAction = URLQueryItem(name: "action", value: "upsert")
let (data, response) = try await apiCall.post(endPoint: RESOURCEPATH, body: document, queryParameters: [upsertAction])
if let result = data {
if let responseErr = try? decoder.decode(ApiResponse.self, from: result) {
if (responseErr.message == "Not Found") {
throw ResponseError.invalidCollection(desc: "Collection \(self.collectionName) \(responseErr.message)")
}
throw ResponseError.documentAlreadyExists(desc: responseErr.message)
}
}
return (data, response)
}

public func delete(filter: String, batchSize: Int? = nil) async throws -> (Data?, URLResponse?) {
var deleteQueryParams: [URLQueryItem] =
[
URLQueryItem(name: "filter_by", value: filter)
]
if let givenBatchSize = batchSize {
deleteQueryParams.append(URLQueryItem(name: "batch_size", value: String(givenBatchSize)))
}
let (data, response) = try await apiCall.delete(endPoint: "\(RESOURCEPATH)", queryParameters: deleteQueryParams)
return (data, response)

}

public func search<T>(_ searchParameters: SearchParameters, for: T.Type) async throws -> (SearchResult<T>?, URLResponse?) {
Expand Down Expand Up @@ -190,4 +219,9 @@ public struct Documents {
let (data, response) = try await apiCall.post(endPoint: "\(RESOURCEPATH)/import", body: documents, queryParameters: [importAction])
return (data, response)
}

public func export() async throws -> (Data?, URLResponse?) {
let (data, response) = try await apiCall.get(endPoint: "\(RESOURCEPATH)/export")
return (data, response)
}
}
5 changes: 5 additions & 0 deletions Sources/Typesense/Errors.swift
Original file line number Diff line number Diff line change
Expand Up @@ -16,4 +16,9 @@ public enum DataError: Error {
public enum ResponseError: Error {
case collectionAlreadyExists(desc: String)
case collectionDoesNotExist(desc: String)
case documentAlreadyExists(desc: String)
case documentDoesNotExist(desc: String)
case invalidCollection(desc: String)
case apiKeyNotFound(desc: String)
case aliasNotFound(desc: String)
}
6 changes: 3 additions & 3 deletions Sources/Typesense/Models/ApiKey.swift
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,10 @@ public struct ApiKey: Codable {
public var collections: [String]
public var expiresAt: Int64?
public var _id: Int64
public var value: String
public var valuePrefix: String
public var value: String?
public var valuePrefix: String?

public init(_description: String? = nil, actions: [String], collections: [String], expiresAt: Int64? = nil, _id: Int64, value: String, valuePrefix: String) {
public init(_description: String? = nil, actions: [String], collections: [String], expiresAt: Int64? = nil, _id: Int64, value: String? = nil, valuePrefix: String? = nil) {
self._description = _description
self.actions = actions
self.collections = collections
Expand Down
61 changes: 61 additions & 0 deletions Sources/Typesense/Operations.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import Foundation

public struct Operations {
var apiCall: ApiCall
var RESOURCEPATH = "operations"

public init(config: Configuration) {
apiCall = ApiCall(config: config)
}

public func getHealth() async throws -> (HealthStatus?, URLResponse?) {
let (data, response) = try await apiCall.get(endPoint: "health")
if let result = data {
let health = try decoder.decode(HealthStatus.self, from: result)
return (health, response)
}
return (nil, nil)
}

public func getStats() async throws -> (Data?, URLResponse?) {
let (data, response) = try await apiCall.get(endPoint: "stats.json")
return (data, response)
}

public func getMetrics() async throws -> (Data?, URLResponse?) {
let (data, response) = try await apiCall.get(endPoint: "metrics.json")
return (data, response)
}

public func vote() async throws -> (SuccessStatus?, URLResponse?) {
let (data, response) = try await apiCall.post(endPoint: "\(RESOURCEPATH)/vote", body: Data())
if let result = data {
let success = try decoder.decode(SuccessStatus.self, from: result)
return (success, response)
}
return (nil, nil)
}

public func snapshot(path: String? = "/tmp/typesense-data-snapshot") async throws -> (SuccessStatus?, URLResponse?) {
let snapshotQueryParam = URLQueryItem(name: "snapshot_path", value: path)
let (data, response) = try await apiCall.post(endPoint: "\(RESOURCEPATH)/snapshot", body: Data(), queryParameters: [snapshotQueryParam])
if let result = data {
let success = try decoder.decode(SuccessStatus.self, from: result)
return (success, response)
}
return (nil, nil)
}

public func toggleSlowRequestLog(seconds: Float) async throws -> (SuccessStatus?, URLResponse?) {
let durationInMs = seconds * 1000
let slowReq = SlowRequest(durationInMs)
let slowReqData = try encoder.encode(slowReq)
let (data, response) = try await apiCall.post(endPoint: "/config", body: slowReqData)
if let result = data {
let success = try decoder.decode(SuccessStatus.self, from: result)
return (success, response)
}
return (nil, nil)
}

}
12 changes: 12 additions & 0 deletions Sources/Typesense/Shared/Coders.swift
Original file line number Diff line number Diff line change
Expand Up @@ -31,3 +31,15 @@ public enum ActionModes: String {
case upsert = "upsert"
case update = "update"
}

public struct SlowRequest: Codable {
public var logSlowRequestsTimeMs: Float?

public init(_ timeInMS: Float? = nil) {
self.logSlowRequestsTimeMs = timeInMS
}

public enum CodingKeys: String, CodingKey {
case logSlowRequestsTimeMs = "log-slow-requests-time-ms"
}
}
Loading