Skip to content

Commit

Permalink
feat: added loadDocumentForKey to LimitedFirestoreDocumentAccessor
Browse files Browse the repository at this point in the history
- AbstractFirestoreDocumentWithParent no longer requires the parent's document reference passed, as it is safe to retrieve it from the accessor
  • Loading branch information
dereekb committed May 31, 2022
1 parent c846fee commit 96958b8
Show file tree
Hide file tree
Showing 9 changed files with 133 additions and 47 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,7 @@ export function guestbookEntryFirestoreCollectionFactory(firestoreContext: Fires
return firestoreContext.firestoreCollectionWithParent({
itemsPerPage: 50,
collection: factory(parent),
makeDocument: (accessor, documentAccessor) => new GuestbookEntryDocument(parent.documentRef, accessor, documentAccessor),
makeDocument: (accessor, documentAccessor) => new GuestbookEntryDocument(accessor, documentAccessor),
firestoreContext,
parent
});
Expand All @@ -133,7 +133,7 @@ export function guestbookEntryFirestoreCollectionGroup(firestoreContext: Firesto
return firestoreContext.firestoreCollectionGroup({
itemsPerPage: 50,
queryLike: guestbookEntryCollectionReference(firestoreContext),
makeDocument: (accessor, documentAccessor) => new GuestbookEntryDocument(undefined, accessor, documentAccessor),
makeDocument: (accessor, documentAccessor) => new GuestbookEntryDocument(accessor, documentAccessor),
firestoreContext
});
}
1 change: 0 additions & 1 deletion packages/firebase-server/src/lib/auth/auth.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -258,7 +258,6 @@ export abstract class AbstractFirebaseServerAuthService<U extends FirebaseServer

context(context: functions.https.CallableContext): C {
assertIsContextWithAuthData(context);
context.auth.token;
return this._context(context);
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { FirestoreAccessorDriver, CollectionReference, Firestore, TransactionFunction, DocumentReference, TransactionFirestoreDocumentContextFactory, WriteBatchFirestoreDocumentContextFactory } from '@dereekb/firebase';
import { FirestoreAccessorDriver, CollectionReference, Firestore, TransactionFunction, DocumentReference, TransactionFirestoreDocumentContextFactory, WriteBatchFirestoreDocumentContextFactory, DocumentData } from '@dereekb/firebase';
import { batch } from '@dereekb/util';
import { CollectionGroup, CollectionReference as GoogleCloudCollectionReference, DocumentReference as GoogleCloudDocumentReference, Firestore as GoogleCloudFirestore } from '@google-cloud/firestore';
import { writeBatchDocumentContext } from './driver.accessor.batch';
Expand Down Expand Up @@ -53,6 +53,7 @@ export function docRefForPath<T>(start: DocRefForPathInput, path?: string, pathS
export function firestoreClientAccessorDriver(): FirestoreAccessorDriver {
return {
doc: <T>(collection: CollectionReference<T>, path?: string, ...pathSegments: string[]) => docRefForPath(collection as GoogleCloudCollectionReference, path, pathSegments) as DocumentReference<T>,
docAtPath: <T>(firestore: Firestore, fullPath: string) => (firestore as GoogleCloudFirestore).doc(fullPath) as DocumentReference<T>,
collectionGroup: <T>(firestore: Firestore, collectionId: string) => (firestore as GoogleCloudFirestore).collectionGroup(collectionId) as CollectionGroup<T>,
collection: <T>(firestore: Firestore, path: string, ...pathSegments: string[]) => collectionRefForPath(firestore as GoogleCloudFirestore, path, pathSegments) as CollectionReference<T>,
subcollection: <T>(document: DocumentReference, path: string, ...pathSegments: string[]) => collectionRefForPath(document as GoogleCloudDocumentReference, path, pathSegments) as CollectionReference<T>,
Expand Down
9 changes: 5 additions & 4 deletions packages/firebase/src/lib/client/firestore/driver.accessor.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { Firestore, runTransaction } from '@firebase/firestore';
import { Firestore as FirebaseFirestore, runTransaction } from '@firebase/firestore';
import { doc, collection, writeBatch, Transaction, collectionGroup } from 'firebase/firestore';
import { FirestoreAccessorDriver } from '../../common/firestore/driver/accessor';
import { FirestoreAccessorDriverCollectionGroupFunction, FirestoreAccessorDriverCollectionRefFunction, FirestoreAccessorDriverDocumentRefFunction, FirestoreAccessorDriverSubcollectionRefFunction, TransactionFunction } from '../../common/firestore/driver';
import { FirestoreAccessorDriverCollectionGroupFunction, FirestoreAccessorDriverCollectionRefFunction, FirestoreAccessorDriverDocumentRefFunction, FirestoreAccessorDriverFullPathDocumentRefFunction, FirestoreAccessorDriverSubcollectionRefFunction, TransactionFunction } from '../../common/firestore/driver';
import { writeBatchDocumentContext } from './driver.accessor.batch';
import { defaultFirestoreDocumentContext } from './driver.accessor.default';
import { transactionDocumentContext } from './driver.accessor.transaction';
Expand All @@ -11,14 +11,15 @@ import { WriteBatchFirestoreDocumentContextFactory } from '../../common/firestor
export function firestoreClientAccessorDriver(): FirestoreAccessorDriver {
return {
doc: doc as unknown as FirestoreAccessorDriverDocumentRefFunction,
docAtPath: doc as unknown as FirestoreAccessorDriverFullPathDocumentRefFunction,
collectionGroup: collectionGroup as unknown as FirestoreAccessorDriverCollectionGroupFunction,
collection: collection as unknown as FirestoreAccessorDriverCollectionRefFunction,
subcollection: collection as unknown as FirestoreAccessorDriverSubcollectionRefFunction,
transactionFactoryForFirestore:
(firestore) =>
async <T>(fn: TransactionFunction<T>) =>
await runTransaction(firestore as Firestore, fn as (transaction: Transaction) => Promise<T>),
writeBatchFactoryForFirestore: (firestore) => () => writeBatch(firestore as Firestore),
await runTransaction(firestore as FirebaseFirestore, fn as (transaction: Transaction) => Promise<T>),
writeBatchFactoryForFirestore: (firestore) => () => writeBatch(firestore as FirebaseFirestore),
defaultContextFactory: defaultFirestoreDocumentContext,
transactionContextFactory: transactionDocumentContext as TransactionFirestoreDocumentContextFactory,
writeBatchContextFactory: writeBatchDocumentContext as unknown as WriteBatchFirestoreDocumentContextFactory
Expand Down
45 changes: 34 additions & 11 deletions packages/firebase/src/lib/common/firestore/accessor/document.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,9 @@ import { Observable } from 'rxjs';
import { FirestoreAccessorDriverRef } from '../driver/accessor';
import { DocumentReference, CollectionReference, Transaction, WriteBatch, DocumentSnapshot, SnapshotOptions, WriteResult } from '../types';
import { createOrUpdateWithAccessorSet, dataFromSnapshotStream, FirestoreDocumentDataAccessor } from './accessor';
import { CollectionReferenceRef, DocumentReferenceRef } from '../reference';
import { CollectionReferenceRef, DocumentReferenceRef, FirestoreContextReference } from '../reference';
import { FirestoreDocumentContext } from './context';
import { build, Maybe } from '@dereekb/util';
import { build, Maybe, ModelKey } from '@dereekb/util';

export interface FirestoreDocument<T, A extends FirestoreDocumentDataAccessor<T> = FirestoreDocumentDataAccessor<T>> extends DocumentReferenceRef<T>, CollectionReferenceRef<T> {
readonly accessor: A;
Expand All @@ -32,7 +32,6 @@ export abstract class AbstractFirestoreDocument<T, D extends AbstractFirestoreDo
}

get collection(): CollectionReference<T> {
// TODO: Only works if the documentRef has access to the parent ref
return this.accessor.documentRef.parent as CollectionReference<T>;
}

Expand Down Expand Up @@ -71,6 +70,20 @@ export interface LimitedFirestoreDocumentAccessor<T, D extends FirestoreDocument
* @param document
*/
loadDocumentFrom(document: FirestoreDocument<T>): D;

/**
* Loads a document from the datastore with the given key/full path.
*
* @param ref
*/
loadDocumentForKey(fullPath: ModelKey): D;

/**
* Creates a document ref with a key/full path.
*
* @param ref
*/
documentRefForKey(fullPath: ModelKey): DocumentReference<T>;
}

export interface FirestoreDocumentAccessor<T, D extends FirestoreDocument<T> = FirestoreDocument<T>> extends LimitedFirestoreDocumentAccessor<T, D>, CollectionReferenceRef<T>, FirestoreAccessorDriverRef {
Expand All @@ -89,6 +102,7 @@ export interface FirestoreDocumentAccessor<T, D extends FirestoreDocument<T> = F
loadDocumentForPath(path: string, ...pathSegments: string[]): D;

/**
* Creates a document ref relative to the current context and given the input path.
*
* @param path
* @param pathSegments
Expand Down Expand Up @@ -122,12 +136,13 @@ export interface LimitedFirestoreDocumentAccessorFactory<T, D extends FirestoreD
/**
* FirestoreDocumentAccessor configuration.
*/
export interface LimitedFirestoreDocumentAccessorFactoryConfig<T, D extends FirestoreDocument<T> = FirestoreDocument<T>> extends FirestoreAccessorDriverRef {
export interface LimitedFirestoreDocumentAccessorFactoryConfig<T, D extends FirestoreDocument<T> = FirestoreDocument<T>> extends FirestoreContextReference, FirestoreAccessorDriverRef {
readonly makeDocument: FirestoreDocumentFactoryFunction<T, D>;
}

export function limitedFirestoreDocumentAccessorFactory<T, D extends FirestoreDocument<T> = FirestoreDocument<T>>(config: LimitedFirestoreDocumentAccessorFactoryConfig<T, D>): LimitedFirestoreDocumentAccessorFactoryFunction<T, D> {
const { firestoreAccessorDriver } = config;
const { firestoreContext, firestoreAccessorDriver } = config;

return (context?: FirestoreDocumentContext<T>) => {
const databaseContext: FirestoreDocumentContext<T> = context ?? config.firestoreAccessorDriver.defaultContextFactory();
const dataAccessorFactory = databaseContext.accessorFactory;
Expand All @@ -137,11 +152,22 @@ export function limitedFirestoreDocumentAccessorFactory<T, D extends FirestoreDo
return config.makeDocument(accessor, documentAccessor);
}

function documentRefForKey(fullPath: ModelKey): DocumentReference<T> {
return firestoreAccessorDriver.docAtPath(firestoreContext.firestore, fullPath);
}

function loadDocumentForKey(fullPath: ModelKey): D {
const ref = documentRefForKey(fullPath);
return loadDocument(ref);
}

const documentAccessor: LimitedFirestoreDocumentAccessor<T, D> = {
loadDocumentFrom(document: FirestoreDocument<T>): D {
return loadDocument(document.documentRef);
},
loadDocument,
documentRefForKey,
loadDocumentForKey,
firestoreAccessorDriver,
databaseContext
};
Expand All @@ -164,7 +190,7 @@ export type FirestoreDocumentAccessorFactory<T, D extends FirestoreDocument<T> =
/**
* FirestoreDocumentAccessor configuration.
*/
export interface FirestoreDocumentAccessorFactoryConfig<T, D extends FirestoreDocument<T> = FirestoreDocument<T>> extends CollectionReferenceRef<T>, FirestoreAccessorDriverRef {
export interface FirestoreDocumentAccessorFactoryConfig<T, D extends FirestoreDocument<T> = FirestoreDocument<T>> extends CollectionReferenceRef<T>, LimitedFirestoreDocumentAccessorFactoryConfig<T, D> {
readonly makeDocument: FirestoreDocumentFactoryFunction<T, D>;
}

Expand Down Expand Up @@ -251,15 +277,12 @@ export interface FirestoreDocumentWithParent<P, T, A extends FirestoreDocumentDa
}

export abstract class AbstractFirestoreDocumentWithParent<P, T, D extends AbstractFirestoreDocument<T, any, any>, A extends FirestoreDocumentDataAccessor<T> = FirestoreDocumentDataAccessor<T>> extends AbstractFirestoreDocument<T, D, A> implements FirestoreDocumentWithParent<P, T, A> {
private _parent: DocumentReference<P>;

get parent() {
return this._parent;
return (this.accessor.documentRef.parent as CollectionReference<T>).parent as DocumentReference<P>;
}

constructor(parent: Maybe<DocumentReference<P>>, accessor: A, documentAccessor: LimitedFirestoreDocumentAccessor<T, D>) {
constructor(accessor: A, documentAccessor: LimitedFirestoreDocumentAccessor<T, D>) {
super(accessor, documentAccessor);
this._parent = parent ?? ((accessor.documentRef.parent as CollectionReference<T>).parent as DocumentReference<P>);
}
}

Expand Down
2 changes: 2 additions & 0 deletions packages/firebase/src/lib/common/firestore/driver/accessor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,14 @@ export type FirestoreAccessorDriverCollectionGroupFunction = <T = DocumentData>(
export type FirestoreAccessorDriverCollectionRefFunction = <T = DocumentData>(firestore: Firestore, path: string, ...pathSegments: string[]) => CollectionReference<T>;
export type FirestoreAccessorDriverSubcollectionRefFunction = <T = DocumentData>(document: DocumentReference, path: string, ...pathSegments: string[]) => CollectionReference<T>;
export type FirestoreAccessorDriverDocumentRefFunction = <T = DocumentData>(collection: CollectionReference<T>, path?: string, ...pathSegments: string[]) => DocumentReference<T>;
export type FirestoreAccessorDriverFullPathDocumentRefFunction = <T = DocumentData>(firestore: Firestore, fullPath: string) => DocumentReference<T>;

/**
* A driver to use for query functionality.
*/
export interface FirestoreAccessorDriver extends FirestoreTransactionFactoryDriver, FirestoreWriteBatchFactoryDriver {
readonly doc: FirestoreAccessorDriverDocumentRefFunction;
readonly docAtPath: FirestoreAccessorDriverFullPathDocumentRefFunction;
readonly collectionGroup: FirestoreAccessorDriverCollectionGroupFunction;
readonly collection: FirestoreAccessorDriverCollectionRefFunction;
readonly subcollection: FirestoreAccessorDriverSubcollectionRefFunction;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,5 +19,6 @@ export function allChildDocumentsUnderParent<P>(parentRef: DocumentReference<P>)
*/
export function allChildDocumentsUnderParentPath(parentPath: string): FirestoreQueryConstraint[] {
// https://medium.com/firebase-developers/how-to-query-collections-in-firestore-under-a-certain-path-6a0d686cebd2
return [orderByDocumentId(), startAtValue(parentPath), endAtValue(parentPath + '\uf8ff')];
// https://medium.com/firelayer/save-money-on-the-list-query-in-firestore-26ef9bee5474 for restricting
return [orderByDocumentId(), startAtValue(parentPath), endAtValue(parentPath + '\u0000')];
}
8 changes: 4 additions & 4 deletions packages/firebase/test/src/lib/common/firestore.mock.item.ts
Original file line number Diff line number Diff line change
Expand Up @@ -199,7 +199,7 @@ export function mockItemSubItemFirestoreCollection(firestoreContext: FirestoreCo
return firestoreContext.firestoreCollectionWithParent({
itemsPerPage: 50,
collection: factory(parent),
makeDocument: (a, d) => new MockItemSubItemDocument(parent.documentRef, a, d),
makeDocument: (a, d) => new MockItemSubItemDocument(a, d),
firestoreContext,
parent
});
Expand All @@ -216,7 +216,7 @@ export function mockItemSubItemFirestoreCollectionGroup(firestoreContext: Firest
return firestoreContext.firestoreCollectionGroup({
itemsPerPage: 50,
queryLike: mockItemSubItemCollectionReference(firestoreContext),
makeDocument: (accessor, documentAccessor) => new MockItemSubItemDocument(undefined, accessor, documentAccessor),
makeDocument: (accessor, documentAccessor) => new MockItemSubItemDocument(accessor, documentAccessor),
firestoreContext
});
}
Expand Down Expand Up @@ -269,7 +269,7 @@ export function mockItemDeepSubItemFirestoreCollection(firestoreContext: Firesto
return firestoreContext.firestoreCollectionWithParent({
itemsPerPage: 50,
collection: factory(parent),
makeDocument: (a, d) => new MockItemDeepSubItemDocument(parent.documentRef, a, d),
makeDocument: (a, d) => new MockItemDeepSubItemDocument(a, d),
firestoreContext,
parent
});
Expand All @@ -286,7 +286,7 @@ export function mockItemDeepSubItemFirestoreCollectionGroup(firestoreContext: Fi
return firestoreContext.firestoreCollectionGroup({
itemsPerPage: 50,
queryLike: mockItemDeepSubItemCollectionReference(firestoreContext),
makeDocument: (accessor, documentAccessor) => new MockItemDeepSubItemDocument(undefined, accessor, documentAccessor),
makeDocument: (accessor, documentAccessor) => new MockItemDeepSubItemDocument(accessor, documentAccessor),
firestoreContext
});
}
Loading

0 comments on commit 96958b8

Please sign in to comment.