From 9f1e32d4de9be58eb3bda2e1a48fdef5e791e77b Mon Sep 17 00:00:00 2001 From: Sasha Koss Date: Mon, 12 Aug 2019 14:14:15 +0200 Subject: [PATCH] Finish documenting the source code --- .clocignore | 3 ++ src/add/index.ts | 6 +-- src/all/index.ts | 4 +- src/batch/index.ts | 44 +++++++-------- src/clear/index.ts | 6 +-- src/collection/index.ts | 11 ++-- src/cursor/index.ts | 12 +++-- src/data/index.ts | 36 ++++++++----- src/doc/index.ts | 27 ++++++++++ src/field/index.ts | 21 ++++++++ src/get/index.ts | 25 +++++++++ src/group/index.ts | 31 ++++++++++- src/limit/index.ts | 20 +++++++ src/onAll/index.ts | 23 ++++++++ src/onGet/index.ts | 18 +++++++ src/onQuery/index.ts | 33 ++++++++++++ src/order/index.ts | 44 ++++++++++++++- src/query/index.ts | 32 ++++++++++- src/ref/index.ts | 77 ++++++++++++++------------ src/subcollection/index.ts | 38 +++++++++++-- src/subcollection/test.ts | 8 +++ src/transaction/index.ts | 107 ++++++++++++++++++++++++++++++++++--- src/update/index.ts | 4 +- src/value/index.ts | 68 +++++++++++++++++++++++ src/where/index.ts | 47 ++++++++++++++++ 25 files changed, 644 insertions(+), 101 deletions(-) create mode 100644 .clocignore diff --git a/.clocignore b/.clocignore new file mode 100644 index 00000000..de6e53b1 --- /dev/null +++ b/.clocignore @@ -0,0 +1,3 @@ +node_modules +secrets +lib \ No newline at end of file diff --git a/src/add/index.ts b/src/add/index.ts index d4bd19e0..7bf13bf7 100644 --- a/src/add/index.ts +++ b/src/add/index.ts @@ -7,9 +7,9 @@ import { ref } from '../ref' /** * Adds a new document with a random id to a collection. * - * @param collection - the collection to add to - * @param data - the data to add to - * @returns a promise to the document + * @param collection - The collection to add to + * @param data - The data to add to + * @returns A promise to the document * * @example * import { add, collection } from 'typesaurus' diff --git a/src/all/index.ts b/src/all/index.ts index 710c6773..6eac2dc4 100644 --- a/src/all/index.ts +++ b/src/all/index.ts @@ -7,8 +7,8 @@ import { wrapData } from '../data' /** * Returns all documents in a collection. * - * @param collection - the collection to get all documents from - * @returns a promise to all documents + * @param collection - The collection to get all documents from + * @returns A promise to all documents * * @example * import { all, collection } from 'typesaurus' diff --git a/src/batch/index.ts b/src/batch/index.ts index d34207ba..8395af59 100644 --- a/src/batch/index.ts +++ b/src/batch/index.ts @@ -7,7 +7,9 @@ import { ModelUpdate } from '../update' import { Field } from '../field' /** - * @returns batch API (set, update, clear, commit) + * Creates batch. + * + * @returns Batch API (set, update, clear, commit) * * @example * import { batch, collection } from 'typesaurus' @@ -27,15 +29,15 @@ export function batch() { const firestoreBatch = firestore().batch() /** - * @param ref - the reference to the document to set - * @param data - the document data + * @param ref - The reference to the document to set + * @param data - The document data */ function set(ref: Ref, data: Model): Doc /** - * @param collection - the collection to set document in - * @param id - the id of the document to set - * @param data - the document data + * @param collection - The collection to set document in + * @param id - The id of the document to set + * @param data - The document data */ function set( collection: Collection, @@ -46,7 +48,7 @@ export function batch() { /** * Sets a document to the given data. * - * @returns the document + * @returns The document * * @example * import { batch, collection } from 'typesaurus' @@ -93,9 +95,9 @@ export function batch() { } /** - * @param collection - the collection to update document in - * @param id - the id of the document to update - * @param data - the document data to update + * @param collection - The collection to update document in + * @param id - The id of the document to update + * @param data - The document data to update */ function update( collection: Collection, @@ -104,15 +106,15 @@ export function batch() { ): void /** - * @param ref - the reference to the document to set - * @param data - the document data to update + * @param ref - The reference to the document to set + * @param data - The document data to update */ function update(ref: Ref, data: Field[]): void /** - * @param collection - the collection to update document in - * @param id - the id of the document to update - * @param data - the document data to update + * @param collection - The collection to update document in + * @param id - The id of the document to update + * @param data - The document data to update */ function update( collection: Collection, @@ -121,8 +123,8 @@ export function batch() { ): void /** - * @param ref - the reference to the document to set - * @param data - the document data to update + * @param ref - The reference to the document to set + * @param data - The document data to update */ function update(ref: Ref, data: ModelUpdate): void @@ -186,13 +188,13 @@ export function batch() { } /** - * @param collection - the collection to remove document in - * @param id - the id of the documented to remove + * @param collection - The collection to remove document in + * @param id - The id of the documented to remove */ function clear(collection: Collection, id: string): void /** - * @param ref - the reference to the document to remove + * @param ref - The reference to the document to remove */ function clear(ref: Ref): void @@ -240,7 +242,7 @@ export function batch() { /** * Starts the execution of the operations in the batch. * - * @returns a promise that resolves when the operations are finished + * @returns A promise that resolves when the operations are finished */ async function commit() { await firestoreBatch.commit() diff --git a/src/clear/index.ts b/src/clear/index.ts index df26f92a..1d3a639f 100644 --- a/src/clear/index.ts +++ b/src/clear/index.ts @@ -3,8 +3,8 @@ import { Collection } from '../collection' import { Ref } from '../ref' /** - * @param collection - the collection to remove document in - * @param id - the id of the documented to remove + * @param collection - The collection to remove document in + * @param id - The id of the documented to remove */ async function clear( collection: Collection, @@ -12,7 +12,7 @@ async function clear( ): Promise /** - * @param ref - the reference to the document to remove + * @param ref - The reference to the document to remove */ async function clear(ref: Ref): Promise diff --git a/src/collection/index.ts b/src/collection/index.ts index cb4e76bc..97f1a0f1 100644 --- a/src/collection/index.ts +++ b/src/collection/index.ts @@ -1,20 +1,23 @@ +/** + * The collection type. It contains the path in Firestore. + */ export interface Collection<_Model> { __type__: 'collection' path: string } /** - * Creates collection object. + * Creates a collection object. * - * @param path - the collection path - * @returns collection object + * @param path - The collection path + * @returns The collection object * * @example * import { add, collection } from 'typesaurus' * * type User = { name: string } * const users = collection('users') - * // { __type__: 'collection', path: 'users' } + * //=> { __type__: 'collection', path: 'users' } * * add(users, { name: 'Sasha' }) */ diff --git a/src/cursor/index.ts b/src/cursor/index.ts index 36163888..8ded734e 100644 --- a/src/cursor/index.ts +++ b/src/cursor/index.ts @@ -14,7 +14,8 @@ export interface Cursor { /** * Start the query results after the given value. * - * @param value - the value to end the query results after + * @param value - The value to end the query results after + * @returns The cursor object * * @example * import { startAfter, order, query, collection } from 'typesaurus' @@ -40,7 +41,8 @@ export function startAfter( /** * Start the query results on the given value. * - * @param value - the value to start the query results at + * @param value - The value to start the query results at + * @returns The cursor object * * @example * import { startAt, order, query, collection } from 'typesaurus' @@ -66,7 +68,8 @@ export function startAt( /** * Ends the query results before the given value. * - * @param value - the value to end the query results before + * @param value - The value to end the query results before + * @returns The cursor object * * @example * import { endBefore, order, query, collection } from 'typesaurus' @@ -92,7 +95,8 @@ export function endBefore( /** * Ends the query results on the given value. * - * @param value - the value to end the query results at + * @param value - The value to end the query results at + * @returns The cursor object * * @example * import { endAt, order, query, collection } from 'typesaurus' diff --git a/src/data/index.ts b/src/data/index.ts index f1234d46..989eecd1 100644 --- a/src/data/index.ts +++ b/src/data/index.ts @@ -3,21 +3,23 @@ import { FirestoreFieldValue, FirestoreTimestamp } from '../adaptor' -import { pathToRef, Ref, refToFirebaseDocument } from '../ref' +import { pathToRef, Ref, refToFirestoreDocument } from '../ref' import { UpdateValue } from '../value' /** + * Converts Typesaurus data to Firestore format. It deeply traverse all the data and + * converts values to compatible format. * - * @param value - the value to convert + * @param data - the data to convert */ -export function unwrapData(value: any) { - if (value instanceof Date) { - return FirestoreTimestamp.fromDate(value) - } else if (value && typeof value === 'object') { - if (value.__type__ === 'ref') { - return refToFirebaseDocument(value as Ref) - } else if (value.__type__ === 'value') { - const fieldValue = value as UpdateValue +export function unwrapData(data: any) { + if (data instanceof Date) { + return FirestoreTimestamp.fromDate(data) + } else if (data && typeof data === 'object') { + if (data.__type__ === 'ref') { + return refToFirestoreDocument(data as Ref) + } else if (data.__type__ === 'value') { + const fieldValue = data as UpdateValue switch (fieldValue.kind) { case 'clear': return FirestoreFieldValue.delete() @@ -33,20 +35,26 @@ export function unwrapData(value: any) { } const unwrappedObject: { [key: string]: any } = Object.assign( - Array.isArray(value) ? [] : {}, - value + Array.isArray(data) ? [] : {}, + data ) Object.keys(unwrappedObject).forEach(key => { unwrappedObject[key] = unwrapData(unwrappedObject[key]) }) return unwrappedObject - } else if (value === undefined) { + } else if (data === undefined) { return null } else { - return value + return data } } +/** + * Converts Firestore data to Typesaurus format. It deeply traverse all the + * data and converts values to compatible format. + * + * @param data - the data to convert + */ export function wrapData(data: unknown) { if (data instanceof FirestoreDocumentReference) { return pathToRef(data.path) diff --git a/src/doc/index.ts b/src/doc/index.ts index 4fb6b570..d716a4b7 100644 --- a/src/doc/index.ts +++ b/src/doc/index.ts @@ -1,11 +1,38 @@ import { Ref } from '../ref' +/** + * The document type. It contains the reference in the DB and the model data. + */ export interface Doc { __type__: 'doc' data: Model ref: Ref } +/** + * Creates a document object. + * + * @param ref - The document reference + * @param data - The model data + * @returns The document object + * + * @example + * import { doc, ref, collection } from 'typesaurus' + * + * type User = { name: string } + * const users = collection('users') + * + * doc(ref(users, '00sHm46UWKObv2W7XK9e'), { name: 'Sasha' }) + * //=> { + * //=> __type__: 'doc', + * //=> data: { name: 'Sasha' }, + * //=> ref: {, + * //=> __type__: 'ref' + * //=> collection: { __type__: 'collection', path: 'users' }, + * //=> id: '00sHm46UWKObv2W7XK9e' + * //=> } + * //=> } + */ export function doc(ref: Ref, data: Model): Doc { return { __type__: 'doc', ref, data } } diff --git a/src/field/index.ts b/src/field/index.ts index 4aa1fa1c..3238d23c 100644 --- a/src/field/index.ts +++ b/src/field/index.ts @@ -1,3 +1,6 @@ +/** + * The field type. It contains path to the property and property value. + */ export interface Field<_Model> { key: string | string[] value: any @@ -118,6 +121,24 @@ function field< value: Model[Key1][Key2][Key3][Key4][Key5][Key6][Key7][Key8][Key9][Key10] ): Field +/** + * Creates a field object. + * + * @param key - The field key or key path + * @param value - The value + * @returns The field object + * + * @example + * import { field, update, collection } from 'typesaurus' + * + * type User = { name: string } + * const users = collection('users') + * update(users, '00sHm46UWKObv2W7XK9e', [ + * field('name', 'Sasha Koss'), + * field(['address', 'city'], 'Dimitrovgrad') + * ]) + * //=> Promise + */ function field(key: string | string[], value: any): Field { return { key, value } } diff --git a/src/get/index.ts b/src/get/index.ts index 70233fa3..4cdd9cc2 100644 --- a/src/get/index.ts +++ b/src/get/index.ts @@ -4,13 +4,38 @@ import { doc, Doc } from '../doc' import { ref, Ref } from '../ref' import { wrapData } from '../data' +/** + * @param ref - The reference to the document + */ async function get(ref: Ref): Promise | undefined> +/** + * + * @param collection - The collection to get document from + * @param id - The document id + */ async function get( collection: Collection, id: string ): Promise | undefined> +/** + * Retrieves a document from a collection. + * + * @returns Promise to the document or undefined if not found + * + * @example + * import { get, collection } from 'typesaurus' + * + * type User = { name: string } + * const users = collection('users') + * + * get(users, '00sHm46UWKObv2W7XK9e').then(user => { + * console.log(user) + * //=> { __type__: 'doc', data: { name: 'Sasha' }, ... } + * }) + * // Or using ref get(currentUser.ref) + */ async function get( collectionOrRef: Collection | Ref, maybeId?: string diff --git a/src/group/index.ts b/src/group/index.ts index 2d63845e..1e0d8518 100644 --- a/src/group/index.ts +++ b/src/group/index.ts @@ -1,6 +1,9 @@ import { Collection } from '../collection' import { Subcollection } from '../subcollection' +/** + * The collection group type. It contains the collection name. + */ export interface CollectionGroup<_Model> { __type__: 'collectionGroup' path: string @@ -23,13 +26,37 @@ function group( collections: [CollectionEntity, CollectionEntity, CollectionEntity] ): CollectionGroup +/** + * Creates a collection group object. + * + * @param name - The collection group name. + * @param collections - The collections to create group from. + * + * @example + * import { group, subcollection, collection, ref } from 'typesaurus' + * + * type User = { name: string } + * type Company = { name: string; address: string } + * type Post = { author: Ref; text: string; date?: Date } + * const users = collection('users') + * const companies = collection('companies') + * const posts = collection('posts') + * const userPosts = subcollection('posts', users) + * const companyPosts = subcollection('posts', companies) + * + * const allPosts = group('posts', [posts, userPosts, companyPosts]) + * //=> { __type__: 'collectionGroup', path: 'posts' } + * + * query(allPosts, [where(author, '==', ref(users, '00sHm46UWKObv2W7XK9e'))]) + * //=> Promise + */ function group( - path: string, + name: string, _collections: CollectionEntity[] ): CollectionGroup { return { __type__: 'collectionGroup', - path + path: name } } diff --git a/src/limit/index.ts b/src/limit/index.ts index 35bfdfd5..e68f7379 100644 --- a/src/limit/index.ts +++ b/src/limit/index.ts @@ -1,8 +1,28 @@ +/** + * The limit query type. It contains the limit value. + */ export interface LimitQuery { type: 'limit' number: number } +/** + * Creates a limit query object. It's used to paginate queries. + * + * @param number - The limit value + * @returns The limit object + * + * @example + * import { limit, query, order, startAfter, collection } from 'typesaurus' + * + * type Contact = { name: string; year: number } + * const contacts = collection('contacts') + * + * query(contacts, [ + * order('year', 'asc', [startAfter(2000)]), + * limit(2) + * ]) + */ export function limit(number: number): LimitQuery { return { type: 'limit', diff --git a/src/onAll/index.ts b/src/onAll/index.ts index ffee9a0e..a9db394e 100644 --- a/src/onAll/index.ts +++ b/src/onAll/index.ts @@ -4,6 +4,29 @@ import { doc, Doc } from '../doc' import { ref } from '../ref' import { wrapData } from '../data' +/** + * Subscribes to all documents in a collection. + * + * @param collection - The collection to get all documents from + * @param onResult - The function which is called with all documents array when + * the initial fetch is resolved or the collection updates. + * @param onError - The function is called with error when request fails. + * + * @example + * import { onAll, collection } from 'typesaurus' + * + * type User = { name: string } + * const users = collection('users') + * + * onAll(users, allUsers => { + * console.log(allUsers.length) + * //=> 420 + * console.log(allUsers[0].ref.id) + * //=> '00sHm46UWKObv2W7XK9e' + * console.log(allUsers[0].data) + * //=> { name: 'Sasha' } + * }) + */ export default function onAll( collection: Collection, onResult: (docs: Doc[]) => any, diff --git a/src/onGet/index.ts b/src/onGet/index.ts index 4237f3c4..b0cf4957 100644 --- a/src/onGet/index.ts +++ b/src/onGet/index.ts @@ -8,12 +8,25 @@ type OnResult = (doc: Doc | undefined) => any type OnError = (error: Error) => any +/** + * @param ref - The reference to the document + * @param onResult - The function which is called with the document when + * the initial fetch is resolved or the document updates. + * @param onError - The function is called with error when request fails. + */ function onGet( ref: Ref, onResult: OnResult, onError?: OnError ): () => void +/** + * @param collection - The document collection + * @param id - The document id + * @param onResult - The function which is called with the document when + * the initial fetch is resolved or the document updates. + * @param onError - The function is called with error when request fails. + */ function onGet( collection: Collection, id: string, @@ -21,6 +34,11 @@ function onGet( onError?: OnError ): () => void +/** + * Subscribes to the diven document. + * + * @returns Function that unsubscribes the listener from the updates + */ function onGet( collectionOrRef: Collection | Ref, idOrOnResult: string | OnResult, diff --git a/src/onQuery/index.ts b/src/onQuery/index.ts index bdc0bb56..7efa9411 100644 --- a/src/onQuery/index.ts +++ b/src/onQuery/index.ts @@ -13,11 +13,44 @@ type FirebaseQuery = | FirebaseFirestore.CollectionReference | FirebaseFirestore.Query +// TODO: Refactor with onQuery + +/** + * The query type. + */ export type Query = | OrderQuery | WhereQuery | LimitQuery +/** + * Subscribes to a collection query built using query objects ({@link order | order}, {@link where | where}, {@link limit | limit}). + * + * @param collection - The collection or collection group to query + * @param queries - The query objects + * @param onResult - The function which is called with the query result when + * the initial fetch is resolved or the query result updates. + * @param onError - The function is called with error when request fails. + * @returns Function that unsubscribes the listener from the updates + * + * @example + * import { query, limit, order, startAfter, collection } from 'typesaurus' + * + * type Contact = { name: string; year: number } + * const contacts = collection('contacts') + * + * onQuery(contacts, [ + * order('year', 'asc', [startAfter(2000)]), + * limit(2) + * ], bornAfter2000 => { + * console.log(bornAfter2000) + * //=> 420 + * console.log(bornAfter2000[0].ref.id) + * //=> '00sHm46UWKObv2W7XK9e' + * console.log(bornAfter2000[0].data) + * //=> { name: 'Sasha' } + * }) + */ export default function onQuery( collection: Collection | CollectionGroup, queries: Query[], diff --git a/src/order/index.ts b/src/order/index.ts index 42b561db..f94f3930 100644 --- a/src/order/index.ts +++ b/src/order/index.ts @@ -1,6 +1,9 @@ import { FirestoreOrderByDirection } from '../adaptor' import { Cursor } from '../cursor' +/** + * The order query type. Used to build query. + */ export interface OrderQuery { type: 'order' field: Key @@ -8,21 +11,60 @@ export interface OrderQuery { cursors: Cursor[] | undefined } +/** + * @param field - Apply ascending order on given field + */ function order( field: Key ): OrderQuery +/** + * @param field - Apply ascending order on given field with given cursors + * @param cursors - Cursors that define pagination rules ({@link startAfter}, {@link startAt}, {@link endBefore} and {@link endAt}) + */ function order( field: Key, - method: Cursor[] + cursors: Cursor[] ): OrderQuery +/** + * + * @param field Apply the order to the field + * @param method Used ordering method ('desc' or 'asc') + * @param cursors - Cursors that define pagination rules ({@link startAfter}, {@link startAt}, {@link endBefore} and {@link endAt}) + */ function order( field: Key, method: FirestoreOrderByDirection, cursors?: Cursor[] ): OrderQuery +/** + * Creates order query object with given field, ordering method + * and pagination cursors. + * + * @returns The order query object + * + * @example + * import { order, query, limit, startAfter, collection } from 'typesaurus' + * + * type Contact = { name: string; year: number } + * const contacts = collection('contacts') + * + * query(contacts, [ + * order('year', 'asc', [startAfter(2000)]), + * //=> { + * //=> type: 'order', + * //=> field: 'year', + * //=> method: 'asc', + * //=> cursors: [{ method: 'startAt', value: 2000 }] + * //=> } + * limit(2) + * ]).then(bornAfter2000 => { + * console.log(bornAfter2000) + * //=> 420 + * }) + */ function order( field: Key, maybeMethod?: FirestoreOrderByDirection | Cursor[], diff --git a/src/query/index.ts b/src/query/index.ts index 6ffb68ec..d5cf5832 100644 --- a/src/query/index.ts +++ b/src/query/index.ts @@ -7,17 +7,47 @@ import { OrderQuery } from '../order' import { LimitQuery } from '../limit' import { Cursor, CursorMethod } from '../cursor' import { wrapData, unwrapData } from '../data' -import { CollectionGroup } from '../group/index' +import { CollectionGroup } from '../group' type FirebaseQuery = | FirebaseFirestore.CollectionReference | FirebaseFirestore.Query +// TODO: Refactor with onQuery + +/** + * The query type. + */ export type Query = | OrderQuery | WhereQuery | LimitQuery +/** + * Queries passed collection using query objects ({@link order}, {@link where}, {@link limit}). + * + * @param collection - The collection or collection group to query + * @param queries - The query objects + * @returns The promise to the query results + * + * @example + * import { query, limit, order, startAfter, collection } from 'typesaurus' + * + * type Contact = { name: string; year: number } + * const contacts = collection('contacts') + * + * query(contacts, [ + * order('year', 'asc', [startAfter(2000)]), + * limit(2) + * ]).then(bornAfter2000 => { + * console.log(bornAfter2000) + * //=> 420 + * console.log(bornAfter2000[0].ref.id) + * //=> '00sHm46UWKObv2W7XK9e' + * console.log(bornAfter2000[0].data) + * //=> { name: 'Sasha' } + * }) + */ export async function query( collection: Collection | CollectionGroup, queries: Query[] diff --git a/src/ref/index.ts b/src/ref/index.ts index 332227c2..ec79dbd0 100644 --- a/src/ref/index.ts +++ b/src/ref/index.ts @@ -1,13 +1,36 @@ import firestore from '../adaptor' -import { Collection } from '../collection/index' -import { doc, Doc } from '../doc' +import { Collection } from '../collection' +/** + * The document reference type. + */ export interface Ref { __type__: 'ref' collection: Collection id: string } +/** + * Creates reference object to a document in given collection with given id. + * + * @param collection - The collection to create refernce in + * @param id - The document id + * @returns The reference object + * + * @example + * import { ref, query, collection, where, Ref } from 'typesaurus' + * + * type User = { name: string } + * type Order = { user: Ref, item: string } + * const users = collection('users') + * const orders = collection('orders') + * + * query(orders, [where('user', '==', ref(users, '00sHm46UWKObv2W7XK9e')]) + * .then(userOrders => { + * console.log(userOrders.length) + * //=> 42 + * }) + */ export function ref( collection: Collection, id: string @@ -15,14 +38,32 @@ export function ref( return { __type__: 'ref', collection, id } } +/** + * Generates Firestore path from a reference. + * + * @param ref - The reference to a document + * @returns Firestore path + */ export function getRefPath(ref: Ref) { return [ref.collection.path].concat(ref.id).join('/') } -export function refToFirebaseDocument(ref: Ref) { +/** + * Creates Firestore document from a reference. + * + * @param ref - The reference to create Firestore document from + * @returns Firestore document + */ +export function refToFirestoreDocument(ref: Ref) { return firestore().doc(getRefPath(ref)) } +/** + * Creates a reference from a Firestore path. + * + * @param path - The Firestore path + * @returns Reference to a document + */ export function pathToRef(path: string): Ref { const captures = path.match(/^(.+)\/(.+)$/) if (!captures) throw new Error(`Can't parse path ${path}`) @@ -33,33 +74,3 @@ export function pathToRef(path: string): Ref { id } } - -// export async function setRef( -// ref: Ref, -// data: Model -// ): Promise> { -// const firebaseDocument = refToFirebaseDocument(ref) -// // return doc.set(unwrapValue(data)).then(() => new Document(ref, data)) -// return firebaseDocument.set(data).then(() => doc(ref, data)) -// } - -// export async function getRef( -// ref: Ref -// ): Promise | null> { -// const doc = refToFirebaseDocument(ref) -// const snap = await doc.get() -// const data = snap.data() as Model -// return createItemOrNull(ref, data) -// } - -// export async function deleteRef(ref: Ref): Promise { -// const doc = refToFirebaseDocument(ref) -// return doc.delete().then(() => {}) -// } - -// export function createItemOrNull( -// ref: Ref, -// data: Model -// ): Doc | null { -// return data ? doc(ref, data) : null -// } diff --git a/src/subcollection/index.ts b/src/subcollection/index.ts index ba18ca98..546dfefc 100644 --- a/src/subcollection/index.ts +++ b/src/subcollection/index.ts @@ -1,17 +1,47 @@ import { Ref } from '../ref' -import { Collection, collection } from '../collection/index' +import { Collection, collection } from '../collection' +/** + * The subcollection function type. + */ export type Subcollection = ( - ref: Ref + ref: Ref | string ) => Collection +/** + * Creates a subcollection function which accepts parent document reference + * and returns the subcollection trasnformed into a collection object. + * + * @param name - The subcollection name + * @param parentCollection - The parent collection + * @returns Function which accepts parent document + * + * @example + * import { subcollection, collection, ref, add } from 'typesaurus' + * + * type User = { name: string } + * type Order = { item: string } + * const users = collection('users') + * const userOrders = subcollection('orders') + * + * const sashasOrders = userOrders('00sHm46UWKObv2W7XK9e') + * //=> { __type__: 'collection', path: 'users/00sHm46UWKObv2W7XK9e/orders' } + * // Also accepts reference: + * userOrders(ref(users, '00sHm46UWKObv2W7XK9e'))) + * + * add(sashasOrders, { item: 'Snowboard boots' }) + */ function subcollection( - path: string, + name: string, parentCollection: Collection ): Subcollection { // TODO: Throw an exception when a collection has different name return ref => - collection(`${parentCollection.path}/${ref.id}/${path}`) + collection( + `${parentCollection.path}/${ + typeof ref === 'string' ? ref : ref.id + }/${name}` + ) } export { subcollection } diff --git a/src/subcollection/test.ts b/src/subcollection/test.ts index 7c2a59d4..e74b154a 100644 --- a/src/subcollection/test.ts +++ b/src/subcollection/test.ts @@ -17,5 +17,13 @@ describe('Subcollection', () => { path: 'users/42/posts' }) }) + + it('allows to pass parent document id', () => { + const userPosts = subcollection('posts', users) + assert.deepEqual(userPosts('42'), { + __type__: 'collection', + path: 'users/42/posts' + }) + }) }) }) diff --git a/src/transaction/index.ts b/src/transaction/index.ts index cef1d47e..f56545b9 100644 --- a/src/transaction/index.ts +++ b/src/transaction/index.ts @@ -9,6 +9,9 @@ import update, { ModelUpdate } from '../update' import { Field } from '../field' import clear from '../clear' +/** + * The Transaction API type. + */ export type TransactionAPI = { get: typeof get set: typeof set @@ -16,12 +19,47 @@ export type TransactionAPI = { clear: typeof clear } +/** + * The transaction body function type. + */ export type TransactionFunction = (api: TransactionAPI) => any +/** + * Performs transaction. + * + * @param transactionFn - The transaction body function that accepts transaction API + * @returns Promise that is resolved when transaction is closed + * + * @example + * import { transaction, collection } from 'typesaurus' + * + * type Counter = { count: number } + * const counters = collection('counters') + * + * transaction(async ({ get, set, update, clear }) => { + * const { data: { count } } = await get('420') + * await set(counter, { count: count + 1 }) + * }) + */ export function transaction(transactionFn: TransactionFunction): Promise { return firestore().runTransaction(t => { - // get - + /** + * Retrieves a document from a collection. + * + * @returns Promise to the document or undefined if not found + * + * @example + * import { transaction, collection } from 'typesaurus' + * + * type Counter = { count: number } + * const counters = collection('counters') + * + * transaction(async ({ get, set }) => { + * const counter = await get('420') + * //=> { __type__: 'doc', data: { count: 42 }, ... } + * await set(counter.ref, { count: counter.data.count + 1 }) + * }) + */ async function get( collectionOrRef: Collection | Ref, maybeId?: string @@ -50,8 +88,23 @@ export function transaction(transactionFn: TransactionFunction): Promise { return data ? doc(ref(collection, id), data) : undefined } - // set - + /** + * Sets a document to the given data. + * + * @returns A promise to the document + * + * @example + * import { transaction, collection } from 'typesaurus' + * + * type Counter = { count: number } + * const counters = collection('counters') + * + * transaction(async ({ get, set }) => { + * const counter = await get('420') + * await set(counter.ref, { count: counter.data.count + 1 }) + * //=> { __type__: 'doc', data: { count: 43 }, ... } + * }) + */ async function set( collectionOrRef: Collection | Ref, idOrData: string | Model, @@ -82,8 +135,28 @@ export function transaction(transactionFn: TransactionFunction): Promise { return doc(ref(collection, id), data) } - // update - + /** + * Updates a document. + * + * @returns A promise that resolves when operation is finished + * + * @example + * import { transaction, collection } from 'typesaurus' + * + * type Counter = { count: number } + * const counters = collection('counters') + * + * transaction(async ({ get, set }) => { + * const counter = await get('420') + * await update(counter.ref, { count: counter.data.count + 1 }) + * //=> { __type__: 'doc', data: { count: 43 }, ... } + * // Or using key paths: + * await update(users, '00sHm46UWKObv2W7XK9e', [ + * ['name', 'Sasha Koss'], + * [['address', 'city'], 'Moscow'] + * ]) + * }) + */ async function update( collectionOrRef: Collection | Ref, idOrData: string | Field[] | ModelUpdate, @@ -121,8 +194,26 @@ export function transaction(transactionFn: TransactionFunction): Promise { await t.update(firebaseDoc, unwrapData(updateData)) } - // clear - + /** + * Removes a document. + * + * @example + * import { transaction, collection } from 'typesaurus' + * + * type Counter = { count: number } + * const counters = collection('counters') + * + * transaction(async ({ get, set }) => { + * const counter = await get('420') + * await update(counter.ref, { count: counter.data.count + 1 }) + * //=> { __type__: 'doc', data: { count: 43 }, ... } + * // Or using key paths: + * await update(users, '00sHm46UWKObv2W7XK9e', [ + * ['name', 'Sasha Koss'], + * [['address', 'city'], 'Moscow'] + * ]) + * }) + */ async function clear( collectionOrRef: Collection | Ref, maybeId?: string diff --git a/src/update/index.ts b/src/update/index.ts index ad6fc565..84745854 100644 --- a/src/update/index.ts +++ b/src/update/index.ts @@ -54,7 +54,9 @@ async function update( ): Promise /** - * @returns a promise that resolves when operation is finished + * Updates a document. + * + * @returns A promise that resolves when the operation is finished * * @example * import { update, collection } from 'typesaurus' diff --git a/src/value/index.ts b/src/value/index.ts index 968fb59a..3be19dc6 100644 --- a/src/value/index.ts +++ b/src/value/index.ts @@ -1,3 +1,6 @@ +/** + * Available value kinds. + */ export type ValueKind = | 'clear' | 'increment' @@ -5,36 +8,57 @@ export type ValueKind = | 'arrayRemove' | 'serverDate' +/** + * The clear value type. + */ export type ValueClear = { __type__: 'value' kind: 'clear' } +/** + * The increment value type. It holds the increment value. + */ export type ValueIncrement = { __type__: 'value' kind: 'increment' number: number } +/** + * The array union value type. It holds the payload to union. + */ export type ValueArrayUnion = { __type__: 'value' kind: 'arrayUnion' values: any[] } +/** + * The array remove value type. It holds the data to remove from the target array. + */ export type ValueArrayRemove = { __type__: 'value' kind: 'arrayRemove' values: any[] } +/** + * The server date value type. + */ export interface ValueServerDate extends Date { __type__: 'value' kind: 'serverDate' } +/** + * The value types that have no type constraints. + */ export type AnyUpdateValue = ValueClear +/** + * The value types to use for update operation. + */ export type UpdateValue = T extends number ? AnyUpdateValue | ValueIncrement : T extends Array @@ -62,6 +86,50 @@ function value>( function value(kind: 'serverDate'): ValueServerDate +/** + * Creates a value object. + * + * @param kind - The value kind ('clear', 'increment', 'arrayUnion', 'arrayRemove' or 'serverDate') + * @param payload - The payload if required by the kind + * + * @example + * import { value, set, update, collection } from 'typesaurus' + * + * type User = { + * name: string, + * friends: number + * interests: string[] + * registrationDate: Date + * note?: string + * } + * + * const users = collection('users') + * + * (async () => { + * await set(users, '00sHm46UWKObv2W7XK9e', { + * name: 'Sasha', + * friends: 123, + * interests: ['snowboarding', 'surfboarding', 'running'], + * // Set server date value to the field + * registrationDate: value('serverDate') + * }) + * + * await update(users, '00sHm46UWKObv2W7XK9e', { + * // Add 2 to the value + * friends: value('increment', 2), + * // Remove 'running' from the interests + * interests: value('arrayRemove', ['running']), + * note: 'Demo' + * }) + * + * await update(users, '00sHm46UWKObv2W7XK9e', { + * // Remove the field + * note: value('clear') + * // Add values to the interests + * interests: value('arrayUnion', ['skateboarding', 'minecraft']) + * }) + * })() + */ function value( kind: ValueKind, payload?: any diff --git a/src/where/index.ts b/src/where/index.ts index 62097994..49dd5f10 100644 --- a/src/where/index.ts +++ b/src/where/index.ts @@ -135,6 +135,30 @@ function where< value: Model[Key1][Key2][Key3][Key4][Key5][Key6][Key7][Key8][Key9][Key10] ): WhereQuery +/** + * Creates where query. + * + * @param field - The field or key path to query + * @param filter - The filter operation ('<', '<=', '==', '>=' or '>') + * @param value - The value to pass to the operation + * @returns The where query object + * + * @example + * import { where, ref, query, collection, Ref } from 'typesaurus' + * + * type User = { name: string } + * type Order = { user: Ref, item: string } + * const users = collection('users') + * const orders = collection('orders') + * + * query(orders, [where('user', '==', ref(users, '00sHm46UWKObv2W7XK9e')]) + * .then(userOrders => { + * console.log(userOrders.length) + * //=> 42 + * }) + * // Or using key paths: + * query(orders, [where(['address', 'city'], '==', 'Moscow']) + */ function where( field: string | string[], filter: FirestoreWhereFilterOp, @@ -150,6 +174,29 @@ function where( export { where } +/** + * Creates where query with array-contains filter operation. + * + * @param field - The field or key path to query + * @param value - The value to pass to the operation + * @returns The where query object + * + * @example + * import { untypedWhereArrayContains, query, collection } from 'typesaurus' + * + * type User = { + * name: string, + * interests: string[] + * } + * + * const users = collection('users') + * + * query(users, [untypedWhereArrayContains('interests', 'snowboarding']) + * .then(snowborders => { + * console.log(snowboarders.length) + * //=> 42 + * }) + */ export function untypedWhereArrayContains( field: string | string[], value: any