Skip to content

Instantly share code, notes, and snippets.

@ishowta
Last active April 26, 2024 05:29
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save ishowta/c769aefa037ae43ef87bc4eb905c6d5b to your computer and use it in GitHub Desktop.
Save ishowta/c769aefa037ae43ef87bc4eb905c6d5b to your computer and use it in GitHub Desktop.
Thin wrapper for firebase-admin firestore compatibility with firebase-js-sdk (experimental)
/**
* @license
* Copyright 2017 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import {
AggregateField,
AggregateQuerySnapshot,
AggregateSpec,
} from '@google-cloud/firestore'
import {
DocumentSnapshot,
FieldPath,
FieldValue,
PartialWithFieldValue,
Query,
QuerySnapshot,
ReadWriteTransactionOptions,
SetOptions,
Transaction,
UpdateData,
WriteBatch,
Firestore,
CollectionReference,
DocumentData,
DocumentReference,
WithFieldValue,
getFirestore as _getFirestore,
} from 'firebase-admin/firestore'
import { isPartialObserver } from '~/api/firebase/alias/observer'
export * from 'firebase-admin/firestore'
export function getFirestore(): Firestore {
return _getFirestore()
}
export function terminate(firestore: Firestore): Promise<void> {
return firestore.terminate()
}
export function arrayRemove(...elements: unknown[]): FieldValue {
return FieldValue.arrayRemove(...elements)
}
export function arrayUnion(...elements: unknown[]): FieldValue {
return FieldValue.arrayUnion(...elements)
}
export function deleteField(): FieldValue {
return FieldValue.delete()
}
export function documentId(): FieldPath {
return FieldPath.documentId()
}
export function increment(n: number): FieldValue {
return FieldValue.increment(n)
}
export function serverTimestamp(): FieldValue {
return FieldValue.serverTimestamp()
}
export function collection(
firestore: Firestore,
path: string
): CollectionReference<DocumentData>
export function collection(
reference: CollectionReference<unknown>,
path: string
): CollectionReference<DocumentData>
export function collection(
reference: DocumentReference,
path: string
): CollectionReference<DocumentData>
export function collection(
parent: Firestore | DocumentReference<unknown> | CollectionReference<unknown>,
path: string
): CollectionReference<DocumentData> {
if (parent instanceof Firestore) {
return parent.collection(path)
} else if (parent instanceof CollectionReference) {
const [pathHead, pathTail] = path.split('/')
return parent.doc(pathHead).collection(pathTail)
} else {
return parent.collection(path)
}
}
export function collectionGroup(
firestore: Firestore,
collectionId: string
): Query<DocumentData> {
return firestore.collectionGroup(collectionId)
}
export function doc(
firestore: Firestore,
path: string
): DocumentReference<DocumentData>
export function doc<T>(
reference: CollectionReference<T>,
path?: string
): DocumentReference<T>
export function doc(
reference: DocumentReference<unknown>,
path: string
): DocumentReference<DocumentData>
export function doc<T>(
parent: Firestore | CollectionReference<T> | DocumentReference<unknown>,
path?: string
// eslint-disable-next-line @typescript-eslint/no-explicit-any
): DocumentReference<any> {
if (parent instanceof Firestore) {
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
return parent.doc(path!)
} else if (parent instanceof CollectionReference) {
if (path) {
return parent.doc(path)
} else {
return parent.doc()
}
} else {
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
const [pathHead, pathTail] = path!.split('/')
return parent.collection(pathHead).doc(pathTail)
}
}
export function getDoc<T>(
reference: DocumentReference<T>
): Promise<DocumentSnapshot<T>> {
return reference.get()
}
export * from './query'
export function getDocs<T>(query: Query<T>): Promise<QuerySnapshot<T>> {
return query.get()
}
export function getCountFromServer(query: Query<unknown>): Promise<
AggregateQuerySnapshot<{
count: AggregateField<number>
}>
> {
return query.count().get()
}
export function addDoc<T>(
reference: CollectionReference<T>,
data: WithFieldValue<T>
): Promise<DocumentReference<T>> {
return reference.add(data)
}
export function updateDoc<T>(
reference: DocumentReference<T>,
data: UpdateData<T>
): Promise<void>
export function updateDoc(
reference: DocumentReference<unknown>,
field: string | FieldPath,
value: unknown
): Promise<void>
export async function updateDoc<T>(
reference: DocumentReference<unknown>,
fieldOrUpdateData: string | FieldPath | UpdateData<T>,
value?: unknown
): Promise<void> {
if (
typeof fieldOrUpdateData === 'string' ||
fieldOrUpdateData instanceof FieldPath
) {
await reference.update(fieldOrUpdateData, value)
return
} else {
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
await reference.update(fieldOrUpdateData!)
return
}
}
export function setDoc<T>(
reference: DocumentReference<T>,
data: WithFieldValue<T>
): Promise<void>
export function setDoc<T>(
reference: DocumentReference<T>,
data: PartialWithFieldValue<T>,
options: SetOptions
): Promise<void>
export async function setDoc<T>(
reference: DocumentReference<T>,
data: PartialWithFieldValue<T>,
options?: SetOptions
): Promise<void> {
if (options) {
await reference.set(data, options)
} else {
await reference.set(data as WithFieldValue<T>)
}
return
}
export async function deleteDoc(
reference: DocumentReference<unknown>
): Promise<void> {
await reference.delete()
return
}
export type FirestoreError = Error
/**
* A function returned by `onSnapshot()` that removes the listener when invoked.
*/
export interface Unsubscribe {
/** Removes the listener when invoked. */
(): void
}
export function onSnapshot<T>(
reference: DocumentReference<T>,
observer: {
next?: (snapshot: DocumentSnapshot<T>) => void
error?: (error: FirestoreError) => void
complete?: () => void
}
): Unsubscribe
export function onSnapshot<T>(
reference: DocumentReference<T>,
onNext: (snapshot: DocumentSnapshot<T>) => void,
onError?: (error: FirestoreError) => void,
onCompletion?: () => void
): Unsubscribe
export function onSnapshot<T>(
query: Query<T>,
observer: {
next?: (snapshot: QuerySnapshot<T>) => void
error?: (error: FirestoreError) => void
complete?: () => void
}
): Unsubscribe
export function onSnapshot<T>(
query: Query<T>,
onNext: (snapshot: QuerySnapshot<T>) => void,
onError?: (error: FirestoreError) => void,
onCompletion?: () => void
): Unsubscribe
export function onSnapshot<T>(
reference: Query<T> | DocumentReference<T>,
...args: unknown[]
): Unsubscribe {
if (isPartialObserver(args[1])) {
const observer = args[1]
// eslint-disable-next-line @typescript-eslint/no-empty-function
return reference.onSnapshot(observer.next ?? (() => {}), observer.error)
} else {
if (reference instanceof DocumentReference) {
return reference.onSnapshot(
args[1] as (snapshot: DocumentSnapshot<T>) => void,
args[2] as (error: FirestoreError) => void
)
} else {
return reference.onSnapshot(
args[1] as (snapshot: QuerySnapshot<T>) => void,
args[2] as (error: FirestoreError) => void
)
}
}
}
export type TransactionOptions = ReadWriteTransactionOptions
export function runTransaction<T>(
firestore: Firestore,
updateFunction: (transaction: Transaction) => Promise<T>,
options?: TransactionOptions
): Promise<T> {
return firestore.runTransaction(updateFunction, options)
}
export function writeBatch(firestore: Firestore): WriteBatch {
return firestore.batch()
}
export function queryEqual<T>(left: Query<T>, right: Query<T>): boolean {
return left.isEqual(right)
}
export function refEqual<T>(
left: DocumentReference<T>,
right: DocumentReference<T>
): boolean
export function refEqual<T>(
left: CollectionReference<T>,
right: CollectionReference<T>
): boolean
export function refEqual<T>(
left: DocumentReference<T> | CollectionReference<T>,
right: DocumentReference<T> | CollectionReference<T>
): boolean {
if (left instanceof DocumentReference) {
return left.isEqual(right as DocumentReference<T>)
} else {
return left.isEqual(right as CollectionReference<T>)
}
}
export function snapshotEqual<T>(
left: DocumentSnapshot<T>,
right: DocumentSnapshot<T>
): boolean
export function snapshotEqual<T>(
left: QuerySnapshot<T>,
right: QuerySnapshot<T>
): boolean
export function snapshotEqual<T>(
left: DocumentSnapshot<T> | QuerySnapshot<T>,
right: DocumentSnapshot<T> | QuerySnapshot<T>
): boolean {
if (left instanceof DocumentSnapshot) {
return left.isEqual(right as DocumentSnapshot<T>)
} else {
return left.isEqual(right as QuerySnapshot<T>)
}
}
export function aggregateQuerySnapshotEqual<T extends AggregateSpec>(
left: AggregateQuerySnapshot<T>,
right: AggregateQuerySnapshot<T>
): boolean {
return left.isEqual(right)
}
/**
* @license
* Copyright 2017 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { FirestoreError } from '~/api/firebase/alias'
export interface JsonObject<T> {
[name: string]: T
}
/**
* Observer/Subscribe interfaces.
*/
export type NextFn<T> = (value: T) => void
export type ErrorFn = (error: FirestoreError) => void
export type CompleteFn = () => void
// Allow for any of the Observer methods to be undefined.
export interface PartialObserver<T> {
next?: NextFn<T>
error?: ErrorFn
complete?: CompleteFn
}
export function isPartialObserver<T>(obj: unknown): obj is PartialObserver<T> {
return implementsAnyMethods(obj, ['next', 'error', 'complete'])
}
/**
* Returns true if obj is an object and contains at least one of the specified
* methods.
*/
function implementsAnyMethods(obj: unknown, methods: string[]): boolean {
if (typeof obj !== 'object' || obj === null) {
return false
}
const object = obj as JsonObject<unknown>
for (const method of methods) {
if (method in object && typeof object[method] === 'function') {
return true
}
}
return false
}
/**
* @license
* Copyright 2020 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import {
DocumentSnapshot,
FieldPath,
Filter,
OrderByDirection,
Query,
WhereFilterOp,
} from 'firebase-admin/firestore'
export const enum Operator {
LESS_THAN = '<',
LESS_THAN_OR_EQUAL = '<=',
EQUAL = '==',
NOT_EQUAL = '!=',
GREATER_THAN = '>',
GREATER_THAN_OR_EQUAL = '>=',
ARRAY_CONTAINS = 'array-contains',
IN = 'in',
NOT_IN = 'not-in',
ARRAY_CONTAINS_ANY = 'array-contains-any',
}
// The operator of a CompositeFilter
export const enum CompositeOperator {
OR = 'or',
AND = 'and',
}
/** Describes the different query constraints available in this SDK. */
export type QueryConstraintType =
| 'where'
| 'orderBy'
| 'limit'
| 'limitToLast'
| 'startAt'
| 'startAfter'
| 'endAt'
| 'endBefore'
/**
* The direction of sorting in an order by.
*/
export const enum Direction {
ASCENDING = 'asc',
DESCENDING = 'desc',
}
export const enum LimitType {
First = 'F',
Last = 'L',
}
/**
* An `AppliableConstraint` is an abstraction of a constraint that can be applied
* to a Firestore query.
*/
export abstract class AppliableConstraint {
/**
* Takes the provided {@link Query} and returns a copy of the {@link Query} with this
* {@link AppliableConstraint} applied.
*/
abstract _apply<T>(query: Query<T>): Query<T>
}
/**
* A `QueryConstraint` is used to narrow the set of documents returned by a
* Firestore query. `QueryConstraint`s are created by invoking {@link where},
* {@link orderBy}, {@link (startAt:1)}, {@link (startAfter:1)}, {@link
* (endBefore:1)}, {@link (endAt:1)}, {@link limit}, {@link limitToLast} and
* can then be passed to {@link (query:1)} to create a new query instance that
* also contains this `QueryConstraint`.
*/
export abstract class QueryConstraint extends AppliableConstraint {
/** The type of this query constraint */
abstract readonly type: QueryConstraintType
/**
* Takes the provided {@link Query} and returns a copy of the {@link Query} with this
* {@link AppliableConstraint} applied.
*/
abstract _apply<T>(query: Query<T>): Query<T>
}
/**
* Creates a new immutable instance of {@link Query} that is extended to also
* include additional query constraints.
*
* @param query - The {@link Query} instance to use as a base for the new
* constraints.
* @param compositeFilter - The {@link QueryCompositeFilterConstraint} to
* apply. Create {@link QueryCompositeFilterConstraint} using {@link and} or
* {@link or}.
* @param queryConstraints - Additional {@link QueryNonFilterConstraint}s to
* apply (e.g. {@link orderBy}, {@link limit}).
* @throws if any of the provided query constraints cannot be combined with the
* existing or new constraints.
*/
export function query<T>(
query: Query<T>,
compositeFilter: QueryCompositeFilterConstraint,
...queryConstraints: QueryNonFilterConstraint[]
): Query<T>
/**
* Creates a new immutable instance of {@link Query} that is extended to also
* include additional query constraints.
*
* @param query - The {@link Query} instance to use as a base for the new
* constraints.
* @param queryConstraints - The list of {@link QueryConstraint}s to apply.
* @throws if any of the provided query constraints cannot be combined with the
* existing or new constraints.
*/
export function query<T>(
query: Query<T>,
...queryConstraints: QueryConstraint[]
): Query<T>
export function query<T>(
query: Query<T>,
queryConstraint: QueryCompositeFilterConstraint | QueryConstraint | undefined,
...additionalQueryConstraints: Array<
QueryConstraint | QueryNonFilterConstraint
>
): Query<T> {
let queryConstraints: AppliableConstraint[] = []
if (queryConstraint instanceof AppliableConstraint) {
queryConstraints.push(queryConstraint)
}
queryConstraints = queryConstraints.concat(additionalQueryConstraints)
for (const constraint of queryConstraints) {
query = constraint._apply(query)
}
return query
}
/**
* A `QueryFieldFilterConstraint` is used to narrow the set of documents returned by
* a Firestore query by filtering on one or more document fields.
* `QueryFieldFilterConstraint`s are created by invoking {@link where} and can then
* be passed to {@link (query:1)} to create a new query instance that also contains
* this `QueryFieldFilterConstraint`.
*/
export class QueryFieldFilterConstraint extends QueryConstraint {
/** The type of this query constraint */
readonly type = 'where'
/**
* @internal
*/
protected constructor(
private readonly _field: string | FieldPath,
private _op: Operator,
private _value: unknown
) {
super()
}
static _create(
_field: string | FieldPath,
_op: Operator,
_value: unknown
): QueryFieldFilterConstraint {
return new QueryFieldFilterConstraint(_field, _op, _value)
}
_apply<T>(query: Query<T>): Query<T> {
return query.where(this._field, this._op, this._value)
}
_standalone(): Filter {
return Filter.where(this._field, this._op, this._value)
}
}
/**
* Creates a {@link QueryFieldFilterConstraint} that enforces that documents
* must contain the specified field and that the value should satisfy the
* relation constraint provided.
*
* @param fieldPath - The path to compare
* @param opStr - The operation string (e.g "&lt;", "&lt;=", "==", "&lt;",
* "&lt;=", "!=").
* @param value - The value for comparison
* @returns The created {@link QueryFieldFilterConstraint}.
*/
export function where(
fieldPath: string | FieldPath,
opStr: WhereFilterOp,
value: unknown
): QueryFieldFilterConstraint {
const op = opStr as Operator
return QueryFieldFilterConstraint._create(fieldPath, op, value)
}
/**
* A `QueryCompositeFilterConstraint` is used to narrow the set of documents
* returned by a Firestore query by performing the logical OR or AND of multiple
* {@link QueryFieldFilterConstraint}s or {@link QueryCompositeFilterConstraint}s.
* `QueryCompositeFilterConstraint`s are created by invoking {@link or} or
* {@link and} and can then be passed to {@link (query:1)} to create a new query
* instance that also contains the `QueryCompositeFilterConstraint`.
*/
export class QueryCompositeFilterConstraint extends AppliableConstraint {
/**
* @internal
*/
protected constructor(
/** The type of this query constraint */
readonly type: 'or' | 'and',
private readonly _queryConstraints: QueryFilterConstraint[]
) {
super()
}
static _create(
type: 'or' | 'and',
_queryConstraints: QueryFilterConstraint[]
): QueryCompositeFilterConstraint {
return new QueryCompositeFilterConstraint(type, _queryConstraints)
}
_apply<T>(query: Query<T>): Query<T> {
return query.where(this._standalone())
}
_standalone(): Filter {
if (this.type === 'or') {
return Filter.or(
...this._queryConstraints.map((queryConstraint) =>
queryConstraint._standalone()
)
)
} else {
return Filter.and(
...this._queryConstraints.map((queryConstraint) =>
queryConstraint._standalone()
)
)
}
}
_getQueryConstraints(): readonly AppliableConstraint[] {
return this._queryConstraints
}
_getOperator(): CompositeOperator {
return this.type === 'and' ? CompositeOperator.AND : CompositeOperator.OR
}
}
/**
* `QueryNonFilterConstraint` is a helper union type that represents
* QueryConstraints which are used to narrow or order the set of documents,
* but that do not explicitly filter on a document field.
* `QueryNonFilterConstraint`s are created by invoking {@link orderBy},
* {@link (startAt:1)}, {@link (startAfter:1)}, {@link (endBefore:1)}, {@link (endAt:1)},
* {@link limit} or {@link limitToLast} and can then be passed to {@link (query:1)}
* to create a new query instance that also contains the `QueryConstraint`.
*/
export type QueryNonFilterConstraint =
| QueryOrderByConstraint
| QueryLimitConstraint
| QueryStartAtConstraint
| QueryEndAtConstraint
/**
* `QueryFilterConstraint` is a helper union type that represents
* {@link QueryFieldFilterConstraint} and {@link QueryCompositeFilterConstraint}.
*/
export type QueryFilterConstraint =
| QueryFieldFilterConstraint
| QueryCompositeFilterConstraint
/**
* Creates a new {@link QueryCompositeFilterConstraint} that is a disjunction of
* the given filter constraints. A disjunction filter includes a document if it
* satisfies any of the given filters.
*
* @param queryConstraints - Optional. The list of
* {@link QueryFilterConstraint}s to perform a disjunction for. These must be
* created with calls to {@link where}, {@link or}, or {@link and}.
* @returns The newly created {@link QueryCompositeFilterConstraint}.
*/
export function or(
...queryConstraints: QueryFilterConstraint[]
): QueryCompositeFilterConstraint {
return QueryCompositeFilterConstraint._create(
CompositeOperator.OR,
queryConstraints
)
}
/**
* Creates a new {@link QueryCompositeFilterConstraint} that is a conjunction of
* the given filter constraints. A conjunction filter includes a document if it
* satisfies all of the given filters.
*
* @param queryConstraints - Optional. The list of
* {@link QueryFilterConstraint}s to perform a conjunction for. These must be
* created with calls to {@link where}, {@link or}, or {@link and}.
* @returns The newly created {@link QueryCompositeFilterConstraint}.
*/
export function and(
...queryConstraints: QueryFilterConstraint[]
): QueryCompositeFilterConstraint {
return QueryCompositeFilterConstraint._create(
CompositeOperator.AND,
queryConstraints
)
}
/**
* A `QueryOrderByConstraint` is used to sort the set of documents returned by a
* Firestore query. `QueryOrderByConstraint`s are created by invoking
* {@link orderBy} and can then be passed to {@link (query:1)} to create a new query
* instance that also contains this `QueryOrderByConstraint`.
*
* Note: Documents that do not contain the orderBy field will not be present in
* the query result.
*/
export class QueryOrderByConstraint extends QueryConstraint {
/** The type of this query constraint */
readonly type = 'orderBy'
/**
* @internal
*/
protected constructor(
private readonly _field: string | FieldPath,
private _direction: Direction
) {
super()
}
static _create(
_field: string | FieldPath,
_direction: Direction
): QueryOrderByConstraint {
return new QueryOrderByConstraint(_field, _direction)
}
_apply<T>(query: Query<T>): Query<T> {
return query.orderBy(this._field, this._direction)
}
}
/**
* Creates a {@link QueryOrderByConstraint} that sorts the query result by the
* specified field, optionally in descending order instead of ascending.
*
* Note: Documents that do not contain the specified field will not be present
* in the query result.
*
* @param fieldPath - The field to sort by.
* @param directionStr - Optional direction to sort by ('asc' or 'desc'). If
* not specified, order will be ascending.
* @returns The created {@link QueryOrderByConstraint}.
*/
export function orderBy(
fieldPath: string | FieldPath,
directionStr: OrderByDirection = 'asc'
): QueryOrderByConstraint {
const direction = directionStr as Direction
return QueryOrderByConstraint._create(fieldPath, direction)
}
/**
* A `QueryLimitConstraint` is used to limit the number of documents returned by
* a Firestore query.
* `QueryLimitConstraint`s are created by invoking {@link limit} or
* {@link limitToLast} and can then be passed to {@link (query:1)} to create a new
* query instance that also contains this `QueryLimitConstraint`.
*/
export class QueryLimitConstraint extends QueryConstraint {
/**
* @internal
*/
protected constructor(
/** The type of this query constraint */
readonly type: 'limit' | 'limitToLast',
private readonly _limit: number,
private readonly _limitType: LimitType
) {
super()
}
static _create(
type: 'limit' | 'limitToLast',
_limit: number,
_limitType: LimitType
): QueryLimitConstraint {
return new QueryLimitConstraint(type, _limit, _limitType)
}
_apply<T>(query: Query<T>): Query<T> {
if (this.type === 'limit') {
return query.limit(this._limit)
} else {
return query.limitToLast(this._limit)
}
}
}
/**
* Creates a {@link QueryLimitConstraint} that only returns the first matching
* documents.
*
* @param limit - The maximum number of items to return.
* @returns The created {@link QueryLimitConstraint}.
*/
export function limit(limit: number): QueryLimitConstraint {
return QueryLimitConstraint._create('limit', limit, LimitType.First)
}
/**
* Creates a {@link QueryLimitConstraint} that only returns the last matching
* documents.
*
* You must specify at least one `orderBy` clause for `limitToLast` queries,
* otherwise an exception will be thrown during execution.
*
* @param limit - The maximum number of items to return.
* @returns The created {@link QueryLimitConstraint}.
*/
export function limitToLast(limit: number): QueryLimitConstraint {
return QueryLimitConstraint._create('limitToLast', limit, LimitType.Last)
}
/**
* A `QueryStartAtConstraint` is used to exclude documents from the start of a
* result set returned by a Firestore query.
* `QueryStartAtConstraint`s are created by invoking {@link (startAt:1)} or
* {@link (startAfter:1)} and can then be passed to {@link (query:1)} to create a
* new query instance that also contains this `QueryStartAtConstraint`.
*/
export class QueryStartAtConstraint extends QueryConstraint {
/**
* @internal
*/
protected constructor(
/** The type of this query constraint */
readonly type: 'startAt' | 'startAfter',
private readonly _docOrFields: Array<unknown | DocumentSnapshot<unknown>>,
private readonly _inclusive: boolean
) {
super()
}
static _create(
type: 'startAt' | 'startAfter',
_docOrFields: Array<unknown | DocumentSnapshot<unknown>>,
_inclusive: boolean
): QueryStartAtConstraint {
return new QueryStartAtConstraint(type, _docOrFields, _inclusive)
}
_apply<T>(query: Query<T>): Query<T> {
if (this.type === 'startAt') {
return query.startAt(this._docOrFields)
} else {
return query.startAfter(this._docOrFields)
}
}
}
/**
* Creates a {@link QueryStartAtConstraint} that modifies the result set to
* start at the provided document (inclusive). The starting position is relative
* to the order of the query. The document must contain all of the fields
* provided in the `orderBy` of this query.
*
* @param snapshot - The snapshot of the document to start at.
* @returns A {@link QueryStartAtConstraint} to pass to `query()`.
*/
export function startAt(
snapshot: DocumentSnapshot<unknown>
): QueryStartAtConstraint
/**
* Creates a {@link QueryStartAtConstraint} that modifies the result set to
* start at the provided fields relative to the order of the query. The order of
* the field values must match the order of the order by clauses of the query.
*
* @param fieldValues - The field values to start this query at, in order
* of the query's order by.
* @returns A {@link QueryStartAtConstraint} to pass to `query()`.
*/
export function startAt(...fieldValues: unknown[]): QueryStartAtConstraint
export function startAt(
...docOrFields: Array<unknown | DocumentSnapshot<unknown>>
): QueryStartAtConstraint {
return QueryStartAtConstraint._create(
'startAt',
docOrFields,
/*inclusive=*/ true
)
}
/**
* Creates a {@link QueryStartAtConstraint} that modifies the result set to
* start after the provided document (exclusive). The starting position is
* relative to the order of the query. The document must contain all of the
* fields provided in the orderBy of the query.
*
* @param snapshot - The snapshot of the document to start after.
* @returns A {@link QueryStartAtConstraint} to pass to `query()`
*/
export function startAfter(
snapshot: DocumentSnapshot<unknown>
): QueryStartAtConstraint
/**
* Creates a {@link QueryStartAtConstraint} that modifies the result set to
* start after the provided fields relative to the order of the query. The order
* of the field values must match the order of the order by clauses of the query.
*
* @param fieldValues - The field values to start this query after, in order
* of the query's order by.
* @returns A {@link QueryStartAtConstraint} to pass to `query()`
*/
export function startAfter(...fieldValues: unknown[]): QueryStartAtConstraint
export function startAfter(
...docOrFields: Array<unknown | DocumentSnapshot<unknown>>
): QueryStartAtConstraint {
return QueryStartAtConstraint._create(
'startAfter',
docOrFields,
/*inclusive=*/ false
)
}
/**
* A `QueryEndAtConstraint` is used to exclude documents from the end of a
* result set returned by a Firestore query.
* `QueryEndAtConstraint`s are created by invoking {@link (endAt:1)} or
* {@link (endBefore:1)} and can then be passed to {@link (query:1)} to create a new
* query instance that also contains this `QueryEndAtConstraint`.
*/
export class QueryEndAtConstraint extends QueryConstraint {
/**
* @internal
*/
protected constructor(
/** The type of this query constraint */
readonly type: 'endBefore' | 'endAt',
private readonly _docOrFields: Array<unknown | DocumentSnapshot<unknown>>,
private readonly _inclusive: boolean
) {
super()
}
static _create(
type: 'endBefore' | 'endAt',
_docOrFields: Array<unknown | DocumentSnapshot<unknown>>,
_inclusive: boolean
): QueryEndAtConstraint {
return new QueryEndAtConstraint(type, _docOrFields, _inclusive)
}
_apply<T>(query: Query<T>): Query<T> {
if (this.type === 'endAt') {
return query.endAt(this._docOrFields)
} else {
return query.endBefore(this._docOrFields)
}
}
}
/**
* Creates a {@link QueryEndAtConstraint} that modifies the result set to end
* before the provided document (exclusive). The end position is relative to the
* order of the query. The document must contain all of the fields provided in
* the orderBy of the query.
*
* @param snapshot - The snapshot of the document to end before.
* @returns A {@link QueryEndAtConstraint} to pass to `query()`
*/
export function endBefore(
snapshot: DocumentSnapshot<unknown>
): QueryEndAtConstraint
/**
* Creates a {@link QueryEndAtConstraint} that modifies the result set to end
* before the provided fields relative to the order of the query. The order of
* the field values must match the order of the order by clauses of the query.
*
* @param fieldValues - The field values to end this query before, in order
* of the query's order by.
* @returns A {@link QueryEndAtConstraint} to pass to `query()`
*/
export function endBefore(...fieldValues: unknown[]): QueryEndAtConstraint
export function endBefore(
...docOrFields: Array<unknown | DocumentSnapshot<unknown>>
): QueryEndAtConstraint {
return QueryEndAtConstraint._create(
'endBefore',
docOrFields,
/*inclusive=*/ false
)
}
/**
* Creates a {@link QueryEndAtConstraint} that modifies the result set to end at
* the provided document (inclusive). The end position is relative to the order
* of the query. The document must contain all of the fields provided in the
* orderBy of the query.
*
* @param snapshot - The snapshot of the document to end at.
* @returns A {@link QueryEndAtConstraint} to pass to `query()`
*/
export function endAt(snapshot: DocumentSnapshot<unknown>): QueryEndAtConstraint
/**
* Creates a {@link QueryEndAtConstraint} that modifies the result set to end at
* the provided fields relative to the order of the query. The order of the field
* values must match the order of the order by clauses of the query.
*
* @param fieldValues - The field values to end this query at, in order
* of the query's order by.
* @returns A {@link QueryEndAtConstraint} to pass to `query()`
*/
export function endAt(...fieldValues: unknown[]): QueryEndAtConstraint
export function endAt(
...docOrFields: Array<unknown | DocumentSnapshot<unknown>>
): QueryEndAtConstraint {
return QueryEndAtConstraint._create('endAt', docOrFields, /*inclusive=*/ true)
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment