Skip to content

Support middleware for hooking into get- and onSnapshot requests #10

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 9 commits into
base: main
Choose a base branch
from
Open
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.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@typed-firestore/react-native",
"version": "1.5.0",
"version": "1.6.0-1",
"description": "Elegant, typed abstractions for Firestore in React Native applications",
"repository": {
"type": "git",
Expand Down
9 changes: 9 additions & 0 deletions src/firestore-types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,12 @@ import type {
QueryConstraint,
} from "@react-native-firebase/firestore";

export type {
QueryConstraint,
UpdateData,
FirestoreError,
} from "@react-native-firebase/firestore";

export type DocumentData = FirebaseFirestoreTypes.DocumentData;

export type DocumentReference<
Expand All @@ -28,6 +34,9 @@ export type Query<T extends DocumentData = DocumentData> =
export type QuerySnapshot<T extends DocumentData = DocumentData> =
FirebaseFirestoreTypes.QuerySnapshot<T>;

export type QueryDocumentSnapshot<T extends DocumentData = DocumentData> =
FirebaseFirestoreTypes.QueryDocumentSnapshot<T>;

export type SnapshotListenOptions =
FirebaseFirestoreTypes.SnapshotListenOptions;

Expand Down
22 changes: 22 additions & 0 deletions src/firestore/delete-doc.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { deleteDoc as deleteDocFirestore } from "@react-native-firebase/firestore";
import type { DocumentData, DocumentReference } from "../firestore-types";
import { MiddlewareManager } from "./middleware-manager";

const manager = new MiddlewareManager<
{ reference: Parameters<typeof deleteDocFirestore>[0] },
ReturnType<typeof deleteDocFirestore>
>(({ reference }) => {
return deleteDocFirestore(reference);
});

export function deleteDoc<T extends DocumentData>(
reference: DocumentReference<T>
) {
return manager.run({ reference });
}

export function experimental_registerDeleteDocMiddleware(
middleware: Parameters<(typeof manager)["use"]>[0]
) {
manager.use(middleware);
}
44 changes: 44 additions & 0 deletions src/firestore/get-doc.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import {
getDoc as getDocFirestore,
getDocFromCache,
getDocFromServer,
} from "@react-native-firebase/firestore";
import type {
DocumentData,
DocumentReference,
DocumentSnapshot,
} from "../firestore-types";
import { MiddlewareManager } from "./middleware-manager";

type GetDocSource = "default" | "cache" | "server";

const manager = new MiddlewareManager<
{
reference: Parameters<typeof getDocFirestore<DocumentData>>[0];
source: GetDocSource;
},
ReturnType<typeof getDocFirestore<DocumentData>>
>(({ reference, source }) => {
switch (source) {
case "cache":
return getDocFromCache(reference);
case "server":
return getDocFromServer(reference);
case "default":
default:
return getDocFirestore(reference);
}
});

export function getDoc<T extends DocumentData>(
reference: DocumentReference<T>,
source: GetDocSource = "default"
) {
return manager.run({ reference, source }) as Promise<DocumentSnapshot<T>>;
}

export function experimental_registerGetDocMiddleware(
middleware: Parameters<(typeof manager)["use"]>[0]
) {
manager.use(middleware);
}
40 changes: 40 additions & 0 deletions src/firestore/get-docs.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import {
getDocs as getDocsFirestore,
getDocsFromCache,
getDocsFromServer,
} from "@react-native-firebase/firestore";
import type { DocumentData, Query, QuerySnapshot } from "../firestore-types";
import { MiddlewareManager } from "./middleware-manager";

type GetDocsSource = "default" | "cache" | "server";

const manager = new MiddlewareManager<
{
query: Parameters<typeof getDocsFirestore<DocumentData>>[0];
source: GetDocsSource;
},
ReturnType<typeof getDocsFirestore<DocumentData>>
>(({ query, source }) => {
switch (source) {
case "cache":
return getDocsFromCache(query);
case "server":
return getDocsFromServer(query);
case "default":
default:
return getDocsFirestore(query);
}
});

export function getDocs<T extends DocumentData>(
query: Query<T>,
source: GetDocsSource = "default"
) {
return manager.run({ query, source }) as Promise<QuerySnapshot<T>>;
}

export function experimental_registerGetDocsMiddleware(
middleware: Parameters<(typeof manager)["use"]>[0]
) {
manager.use(middleware);
}
7 changes: 7 additions & 0 deletions src/firestore/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
export { doc, limit, query } from "@react-native-firebase/firestore";
export * from "./on-doc-snapshot";
export * from "./on-docs-snapshot";
export * from "./get-doc";
export * from "./get-docs";
export * from "./delete-doc";
export * from "./update-doc";
29 changes: 29 additions & 0 deletions src/firestore/middleware-manager.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
export type Next<T, R> = (ctx: T) => R;
export type Middleware<T, R> = (ctx: T, next: Next<T, R>) => R;
export type Handler<T, R> = (ctx: T) => R;

export class MiddlewareManager<T, R> {
private handler: Handler<T, R>;
private middlewares: Middleware<T, R>[] = [];

constructor(handler: Handler<T, R>) {
this.handler = handler;
}

use(middleware: Middleware<T, R>): this {
this.middlewares.push(middleware);
return this;
}

run(initialCtx: T): R {
const dispatch = (i: number, ctx: T): R => {
if (i >= this.middlewares.length) {
return this.handler(ctx);
}
const middleware = this.middlewares[i]!;
return middleware(ctx, (newCtx) => dispatch(i + 1, newCtx));
};

return dispatch(0, initialCtx);
}
}
46 changes: 46 additions & 0 deletions src/firestore/on-doc-snapshot.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import { onSnapshot } from "@react-native-firebase/firestore";
import type {
DocumentData,
DocumentReference,
DocumentSnapshot,
FirestoreError,
SnapshotListenOptions,
} from "../firestore-types";
import { MiddlewareManager } from "./middleware-manager";

const manager = new MiddlewareManager<
{
reference: DocumentReference;
options: SnapshotListenOptions | undefined;
onNext: (snapshot: DocumentSnapshot<DocumentData>) => void;
onError?: (error: FirestoreError) => void;
onCompletion?: () => void;
},
ReturnType<typeof onSnapshot>
>(({ reference, options, onNext, onError, onCompletion }) => {
return options
? onSnapshot(reference, options, onNext, onError, onCompletion)
: onSnapshot(reference, onNext, onError, onCompletion);
});

export function onDocSnapshot<T extends DocumentData>(
reference: DocumentReference<T>,
options: SnapshotListenOptions | undefined,
onNext: (snapshot: DocumentSnapshot<T>) => void,
onError?: (error: FirestoreError) => void,
onCompletion?: () => void
) {
return manager.run({
reference,
options,
onNext: onNext as (snapshot: DocumentSnapshot<DocumentData>) => void,
onError,
onCompletion,
});
}

export function experimental_registerOnDocSnapshotMiddleware(
middleware: Parameters<(typeof manager)["use"]>[0]
) {
manager.use(middleware);
}
46 changes: 46 additions & 0 deletions src/firestore/on-docs-snapshot.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import { onSnapshot } from "@react-native-firebase/firestore";
import type {
DocumentData,
FirestoreError,
Query,
QuerySnapshot,
SnapshotListenOptions,
} from "../firestore-types";
import { MiddlewareManager } from "./middleware-manager";

const manager = new MiddlewareManager<
{
query: Query;
options: SnapshotListenOptions | undefined;
onNext: (snapshot: QuerySnapshot) => void;
onError?: (error: FirestoreError) => void;
onCompletion?: () => void;
},
ReturnType<typeof onSnapshot>
>(({ query, options, onNext, onError, onCompletion }) => {
return options
? onSnapshot(query, options, onNext, onError, onCompletion)
: onSnapshot(query, onNext, onError, onCompletion);
});

export function onDocsSnapshot<T extends DocumentData>(
query: Query<T>,
options: SnapshotListenOptions | undefined,
onNext: (snapshot: QuerySnapshot<T>) => void,
onError?: (error: FirestoreError) => void,
onCompletion?: () => void
) {
return manager.run({
query,
options,
onNext: onNext as (snapshot: QuerySnapshot) => void,
onError,
onCompletion,
});
}

export function experimental_registerOnDocsSnapshotMiddleware(
middleware: Parameters<(typeof manager)["use"]>[0]
) {
manager.use(middleware);
}
30 changes: 30 additions & 0 deletions src/firestore/update-doc.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { updateDoc as updateDocFirestore } from "@react-native-firebase/firestore";
import type {
DocumentData,
DocumentReference,
UpdateData,
} from "../firestore-types";
import { MiddlewareManager } from "./middleware-manager";

const manager = new MiddlewareManager<
{
reference: Parameters<typeof updateDocFirestore>[0];
data: UpdateData<DocumentData>;
},
ReturnType<typeof updateDocFirestore>
>(({ reference, data }) => {
return updateDocFirestore(reference, data);
});

export function updateDoc<T extends DocumentData>(
reference: DocumentReference<T>,
data: UpdateData<T>
) {
return manager.run({ reference, data });
}

export function experimental_registerUpdateDocMiddleware(
middleware: Parameters<(typeof manager)["use"]>[0]
) {
manager.use(middleware);
}
47 changes: 14 additions & 33 deletions src/fork/useCollection.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,11 @@
import {
type FirestoreError,
getDocs,
getDocsFromCache,
getDocsFromServer,
onSnapshot,
} from "@react-native-firebase/firestore";
import { getDocs, onDocsSnapshot } from "../firestore";
import { useCallback, useEffect, useMemo } from "react";
import type { DocumentData, Query, QuerySnapshot } from "~/firestore-types";
import type {
DocumentData,
Query,
QuerySnapshot,
FirestoreError,
} from "~/firestore-types";
import {
useIsFirestoreQueryEqual,
useIsMounted,
Expand All @@ -18,7 +17,6 @@ import type {
CollectionHook,
CollectionOnceHook,
DataOptions,
GetOptions,
OnceDataOptions,
OnceOptions,
Options,
Expand All @@ -39,14 +37,12 @@ export function useCollection_fork<T extends DocumentData>(
setValue(undefined);
return;
}
const unsubscribe = options?.snapshotListenOptions
? onSnapshot(
ref.current,
options.snapshotListenOptions,
setValue,
setError
)
: onSnapshot(ref.current, setValue, setError);
const unsubscribe = onDocsSnapshot(
ref.current,
options?.snapshotListenOptions,
setValue,
setError
);

return () => {
unsubscribe();
Expand All @@ -73,10 +69,9 @@ export function useCollectionOnce_fork<T extends DocumentData>(
setValue(undefined);
return;
}
const get = getDocsFnFromGetOptions(options?.getOptions);

try {
const result = await get(query);
const result = await getDocs(query, options?.getOptions?.source);
if (isMounted) {
setValue(result);
}
Expand Down Expand Up @@ -131,17 +126,3 @@ const getValuesFromSnapshots = <T extends DocumentData>(
): T[] | undefined => {
return useMemo(() => snapshots?.docs.map((doc) => doc.data()), [snapshots]);
};

const getDocsFnFromGetOptions = (
{ source }: GetOptions = { source: "default" }
) => {
switch (source) {
default:
case "default":
return getDocs;
case "cache":
return getDocsFromCache;
case "server":
return getDocsFromServer;
}
};
Loading