QueryableKit allows you to write and perform type-safe Firestore queries.
import QueryableCore
import QueryableFirestore
@Queryable
struct Suburb {
let id: String
let city: String
let population: Int
let neighbours: [Int]
let distanceFromCityCenter: Double
}
let documents = try await Firestore.firestore().collection("suburbs").query([
\Suburb.city == "Melbourne",
\Suburb.population >= 50_000,
Field(\Suburb.neighbours, isAnyOf: [3083,3000]),
Sort(by: \Suburb.distanceFromCityCenter),
Limit(max: 3)
])
.getDocuments()QueryableKit is in active development and can change at any time.
Once you have integrated SPM in your project, you can add QueryableKit inside the dependencies field of your Package.swift:
dependencies: [
.package(url: "https://github.com/peppesapienza/QueryableKit", branch: "main")
]You can then use QueryableCore and/or FirestoreQueryable as target dependency:
dependencies: [
.product(name: "QueryableCore", package: "QueryableKit"),
.product(name: "FirestoreQueryable", package: "QueryableKit")
]The Queryable macro adds everything needed for your type to be supported by FirestoreQueryable and conforms the type to the QueryableModel protocol. For example, if your type's properties perfectly match the Firestore schema, your type will look like this:
import QueryableCore
@Queryable
struct Person {
let name: String
let age: Int
let friends: [Int]
}If your type implements a custom CodingKey, QueryableKit will use it to map each KeyPath to the corresponding value. In the example, using the \City.population will query against the populationCount field.
@Queryable
struct City: Equatable {
let name: String
let population: Int
let suburbs: [String]
enum CodingKeys: String, CodingKey {
case name
case population = "populationCount"
case suburbs
}
}Once your model conforms to QueryableModel either through the macro Queryable or through manual conformance, you can query your data using the CollectionReference method func query(_ predicates: [any QueryablePredicate]) -> Query:
import FirestoreQueryable
try await Firestore.firestore().collection("grades").query([
Field(\Grade.termId, isEqualTo: currentTerm.id),
Field(\Grade.subjectId, isEqualTo: subject.id),
Sort(by: \Grade.date)
])You can use one of the following predicates based on your property type:
Field(\Foo.bar, contains: Value)
Field(\Foo.bar, isAnyOf: [a, b, c])
Field(\Foo.bar, isEqualTo: Value)
Field(\Foo.bar, isGreaterThan: Value)
Field(\Foo.bar, isGreaterThanOrEqualTo: Value)
Field(\Foo.bar, isLessThan: Value)
Field(\Foo.bar, isLessThanOrEqualTo: Value)
Limit(max: Int)
Sort(by: \Foo.bar, descending: Bool)For example, you can't use a isGreaterThan: if your property value is not Comparable or you can't use an isAnyOf or contains if your property is not Collection.