diff --git a/README.md b/README.md index 851b215..3c2dd85 100644 --- a/README.md +++ b/README.md @@ -111,6 +111,29 @@ const { posts: postsWithAuthorAndComments } = await firegraph.resolve(firestore, `) ``` +### Filtering Results + +One of our primary goals is to wrap the Firestore API in its entirety. Filtering +is one of the areas where GraphQL query syntax will really shine: + +``` typescript +const authorId = 'sZOgUC33ijsGSzX17ybT'; +const { posts: postsBySomeAuthor } = await firegraph.resolve(firestore, gql` + query { + posts(where: { + author: ${authorId}, + }) { + id + message + author(matchesKeyFromCollection: "users") { + id + } + } + } +`); +``` + + # Roadmap - [x] Querying values from collections diff --git a/examples/queries/references.graphql b/examples/queries/references.graphql deleted file mode 100644 index e66eb64..0000000 --- a/examples/queries/references.graphql +++ /dev/null @@ -1,18 +0,0 @@ -query { - posts { - id - message - author(matchesKeyFromCollection: "users") { - id - fullName - } - comments { - id - message - author(matchesKeyFromCollection: "users") { - id - fullName - } - } - } -} \ No newline at end of file diff --git a/examples/queries/types.graphql b/examples/queries/types.graphql deleted file mode 100644 index 8ed77f1..0000000 --- a/examples/queries/types.graphql +++ /dev/null @@ -1,13 +0,0 @@ -query { - users { - id - hometown - fullName - birthdate - favoriteColor, - posts { - id - message - } - } -} \ No newline at end of file diff --git a/examples/references.ts b/examples/references.ts deleted file mode 100644 index 325f5fa..0000000 --- a/examples/references.ts +++ /dev/null @@ -1,28 +0,0 @@ -import gql from 'graphql-tag'; -import firegraph from '../src'; -import { firestore } from '../test/firebase'; - -const postsQuery = gql` - query { - posts { - id - message - author(matchesKeyFromCollection: "users") { - id - fullName - } - comments { - id - message - author(matchesKeyFromCollection: "users") { - id - fullName - } - } - } - } -`; - -firegraph.resolve(firestore, postsQuery).then(collections => { - console.log(JSON.stringify(collections, null, 4)); -}); \ No newline at end of file diff --git a/examples/types.ts b/examples/types.ts deleted file mode 100644 index e0b199b..0000000 --- a/examples/types.ts +++ /dev/null @@ -1,23 +0,0 @@ -import gql from 'graphql-tag'; -import firegraph from '../src'; -import { firestore } from '../test/firebase'; - -const userQuery = gql` - query { - users { - id - hometown - fullName - birthdate - favoriteColor, - posts { - id - message - } - } - } -`; - -firegraph.resolve(firestore, userQuery).then(collections => { - console.log(JSON.stringify(collections, null, 4)); -}); \ No newline at end of file diff --git a/examples/where.ts b/examples/where.ts deleted file mode 100644 index 7b096ea..0000000 --- a/examples/where.ts +++ /dev/null @@ -1,16 +0,0 @@ -import gql from 'graphql-tag'; -import firegraph from '../src'; -import { firestore } from '../test/firebase'; - -const whereQuery = gql` - query { - posts(where: { - id: "x" - }) { - id - message - } - } -`; - -console.log(whereQuery); \ No newline at end of file diff --git a/src/firegraph/Collection.ts b/src/firegraph/Collection.ts index a96198e..29cbacc 100644 --- a/src/firegraph/Collection.ts +++ b/src/firegraph/Collection.ts @@ -13,13 +13,26 @@ import { resolveDocument } from './Document'; export async function resolveCollection( store: firebase.firestore.Firestore, collectionName: string, + collectionArgs: { [key:string]: any }, selectionSet: GraphQLSelectionSet ): Promise { + let collectionQuery: any = store.collection(collectionName); let collectionResult: FiregraphCollectionResult = { name: collectionName, docs: [] }; - const collectionSnapshot = await store.collection(collectionName).get(); + if (collectionArgs) { + if (collectionArgs['where']) { + const where = collectionArgs['where']; + where.forEach((filter: any) => { + collectionQuery = collectionQuery.where( + filter['key'], '==', filter['value'] + ); + }); + } + } + + const collectionSnapshot = await collectionQuery.get(); if (selectionSet && selectionSet.selections) { for (let doc of collectionSnapshot.docs) { const documentPath = `${collectionName}/${doc.id}`; diff --git a/src/firegraph/Document.ts b/src/firegraph/Document.ts index 8b8f6e2..c3bedbb 100644 --- a/src/firegraph/Document.ts +++ b/src/firegraph/Document.ts @@ -33,6 +33,8 @@ export async function resolveDocument( if (selectionSet && selectionSet.selections) { let nestedPath: string; const { matchesKeyFromCollection } = args; + + // Document reference. if (matchesKeyFromCollection) { const docId = data[fieldName]; nestedPath = `${matchesKeyFromCollection}/${docId}`; @@ -42,11 +44,14 @@ export async function resolveDocument( selectionSet ); docResult[fieldName] = nestedResult; + + // Nested collection. } else { nestedPath = `${documentPath}/${fieldName}`; const nestedResult = await resolveCollection( store, nestedPath, + args, selectionSet ); docResult[fieldName] = nestedResult.docs; diff --git a/src/firegraph/Where.ts b/src/firegraph/Where.ts index e69de29..206de29 100644 --- a/src/firegraph/Where.ts +++ b/src/firegraph/Where.ts @@ -0,0 +1,9 @@ +export const parseObjectValue = (objectFields: any): any => { + return objectFields.map((field: any) => { + const { name, value } = field; + return { + key: name.value, + value: value.value + }; + }); +} \ No newline at end of file diff --git a/src/index.ts b/src/index.ts index 55bca6c..c8a02be 100644 --- a/src/index.ts +++ b/src/index.ts @@ -2,6 +2,7 @@ import firebase from 'firebase/app'; import 'firebase/firestore'; // Top-level imports related to Firegraph. +import { parseObjectValue } from './firegraph/Where'; import { resolveCollection } from './firegraph/Collection'; import { FiregraphResult } from './types/Firegraph'; @@ -30,15 +31,26 @@ async function resolve( for (let collection of targetCollections) { const { name: { value: collectionName }, - selectionSet + selectionSet, + arguments: collectionArguments, } = collection; + // Parse the GraphQL argument AST into something we can use. + const parsedArgs: any = {}; + collectionArguments.forEach((arg: any) => { + if (arg.value.kind === 'ObjectValue') { + const { fields } = arg.value; + parsedArgs[arg.name.value] = parseObjectValue(fields); + } + }); + // Now we begin to recursively fetch values defined in GraphQL // selection sets. We pass our `firestore` instance to ensure // all selections are done from the same database. const result = await resolveCollection( firestore, collectionName, + parsedArgs, selectionSet ); diff --git a/test/firegraph.test.ts b/test/firegraph.test.ts index 3295c2e..cfbf95a 100644 --- a/test/firegraph.test.ts +++ b/test/firegraph.test.ts @@ -13,7 +13,6 @@ describe('firegraph', () => { } `); - expect(posts).toHaveLength(1); posts.map((post: any) => { expect(post).toHaveProperty('id'); expect(post).toHaveProperty('message'); @@ -70,24 +69,25 @@ describe('firegraph', () => { }); }); - // it('can filter results with WHERE clause', async () => { - // const { posts } = await firegraph.resolve(firestore, gql` - // query { - // posts(where: { - // author: "sZOgUC33ijsGSzX17ybT" - // }) { - // id - // message - // author(matchesKeyFromCollection: "users") { - // id - // } - // } - // } - // `); - // posts.forEach((post: any) => { - // expect(post).toHaveProperty('author'); - // expect(post.author).toHaveProperty('id'); - // expect(post.author.id).toEqual('sZOgUC33ijsGSzX17ybT'); - // }); - // }); + it('can filter results with WHERE clause', async () => { + const authorId = 'sZOgUC33ijsGSzX17ybT'; + const { posts } = await firegraph.resolve(firestore, gql` + query { + posts(where: { + author: ${authorId}, + }) { + id + message + author(matchesKeyFromCollection: "users") { + id + } + } + } + `); + posts.forEach((post: any) => { + expect(post).toHaveProperty('author'); + expect(post.author).toHaveProperty('id'); + expect(post.author.id).toEqual('sZOgUC33ijsGSzX17ybT'); + }); + }); }); \ No newline at end of file