@leaddreamer/firebasefirestorewrapper v1.0.16
FirebaseFirestoreWrapper
A set of helper-wrapper functions around firebase firestore, storage and auth. Intent is to treat Firestore as a hierarchical record-oriented database; originally conceived to port from one database to another.
- FirebaseFirestoreWrapper
- static
- .PaginateFetch
- .PaginateGroupFetch
- .PaginatedListener
- .PAGINATE_CHOICES : enum
- .documentId
- .deleteFieldValue
- .serverTimestampFieldValue
- .MAX_CONCURRENCY : number
- .PAGINATE_INIT : number
- .PAGINATE_PENDING : number
- .PAGINATE_UPDATED : number
- .PAGINATE_DEFAULT : number
- .timestamp()
- .incrementFieldValue(n) ⇒
- .arrayRemoveFieldValue(elements) ⇒ sentinelValue
- .arrayUnionFieldValue(elements) ⇒
- .RecordFromSnapshot(Snap) ⇒ Record
- .DocumentFromRecord(Record) ⇒ object
- .runTransaction(updateFunction) ⇒ Promise.<object>
- .openWriteBatch() ⇒ WriteBatch
- .closeWriteBatch(batch) ⇒ Promise.<void>
- .createUniqueReference(tablePath, refPath) ⇒ DocumentReference
- .dbReference(refPath)
- .writeRecord(tablePath, data, refPath, batch, mergeOption) ⇒ Promise.<Record>
- .writeRecordByRefPath(data, refPath, Transaction, mergeOption) ⇒ Promise.<Record>
- .writeBack(data, Transaction, mergeOption) ⇒ Promise.<Record>
- .collectRecords(tablePath, refPath) ⇒ Promise.<Array.<Record>>
- .collectRecordsByFilter(table, [filterArray], refPath) ⇒ Promise.<Array.<Record>>
- .collectRecordsInGroup(tableName) ⇒ Promise.<Array.<Record>>
- .collectRecordsInGroupByFilter(tableName, [filterArray]) ⇒ Promise.<Array.<Record>>
- .fetchRecord(tablePath, Id, refPath, batch) ⇒ Promise.<(Record|WriteBatch|Transaction)>
- .fetchRecordByRefPath(docRefPath, batch) ⇒ Promise.<Record>
- .fetchRecordByFilter(table, [filterArray], refPath, batch) ⇒ Promise.<(Record|WriteBatch|Transaction)>
- .fetchRecordInGroupByFilter(table, [filterArray], batch) ⇒ Promise.<(Record|WriteBatch|Transaction)>
- .deleteRecord(table, record, refPath, batch) ⇒ Promise.<(Record|WriteBatch|Transaction)>
- .deleteRecordByRefPath(docRefPath, batch) ⇒ Promise.<(Record|WriteBatch|Transaction)>
- .updateRecordFields(recordUpdate) ⇒ Promise.<Record>
- .updateRecordByRefPath(docRefPath, data, batch) ⇒ Promise.<(Record|WriteBatch|Transaction)>
- .writeArrayValue(fieldName, fieldValue, docRefPath, batch) ⇒ Promise.<(Record|WriteBatch|Transaction)>
- .ListenRecords(tablePath, refPath, dataCallback, errCallback) ⇒ callback
- .ListenQuery(table, [filterArray], [sortArray], refPath, dataCallback, errCallback) ⇒ callback
- .ListenCollectionGroupRecords(tablePath, refPath, dataCallback, errCallback) ⇒ callback
- .ListenCollectionGroupQuery(table, [filterArray], [sortArray], dataCallback, errCallback) ⇒ callback
- .ListenRecord(tablePath, Id, refPath, dataCallback, errCallback) ⇒ callback
- .RecordListener : function
- .CollectionListener : function
- .PagingStatus : PAGINATE_INIT | PAGINATE_PENDING | PAGINATE_UPDATED | PAGINATE_DEFAULT
- inner
- ~initialize_firestore(firebase)
- ~Record : object
- ~RecordArray : Record
- static
FirebaseFirestoreWrapper.PaginateFetch
An object to allow for paginating a table read from Firestore. REQUIRES a sorting choice
Kind: static class of FirebaseFirestoreWrapper Properties
Name | Type | Description |
---|---|---|
Query | Query | that forms basis for the table read |
limit | number | page size |
snapshot | QuerySnapshot | last successful snapshot/page fetched |
status | PagingStatus | status of pagination object |
PageForward | function | pages the fetch forward |
PageBack | function | pages the fetch backward |
new exports.PaginateFetch(table, filterArray, sortArray, refPath, limit)
Param | Type | Default | Description |
---|---|---|---|
table | string | a properly formatted string representing the requested collection - always an ODD number of elements | |
filterArray | array | an (optional) 3xn array of filter(i.e. "where") conditions The array is assumed to be sorted in the correct order - i.e. filterArray0 is added first; filterArraylength-1 last returns data as an array of objects (not dissimilar to Redux State objects) with both the documentID and documentReference added as fields. | |
sortArray | array | a 2xn array of sort (i.e. "orderBy") conditions | |
refPath | string | null | (optional) allows "table" parameter to reference a sub-collection of an existing document reference (I use a LOT of structured collections) |
limit | number | page size |
FirebaseFirestoreWrapper.PaginateGroupFetch
An object to allow for paginating a query for table read from Firestore.
Kind: static class of FirebaseFirestoreWrapper Properties
Name | Type | Description |
---|---|---|
Query | Query | that forms basis for the table read |
limit | number | page size |
snapshot | QuerySnapshot | last successful snapshot/page fetched |
status | PagingStatus | status of pagination object |
PageForward | function | Changes the listener to the next page forward |
PageBack | function | Changes the listener to the next page backward |
Unsubscribe | function | returns the unsubscribe function |
new exports.PaginateGroupFetch(group, filterArray, sortArray, limit)
Param | Type | Default | Description |
---|---|---|---|
group | string | a properly formatted string representing the requested collection - always an ODD number of elements | |
filterArray | filterObject | an (optional) 3xn array of filter(i.e. "where") conditions | |
sortArray | sortObject | a 2xn array of sort (i.e. "orderBy") conditions The array(s) are assumed to be sorted in the correct order - i.e. filterArray0 is added first; filterArraylength-1 last returns data as an array of objects (not dissimilar to Redux State objects) with both the documentID and documentReference added as fields. | |
limit | number | (optional) |
FirebaseFirestoreWrapper.PaginatedListener
An object to allow for paginating a listener for table read from Firestore. REQUIRES a sorting choice masks some subscribe/unsubscribe action for paging forward/backward
Kind: static class of FirebaseFirestoreWrapper Properties
Name | Type | Description |
---|---|---|
Query | Query | that forms basis for the table read |
limit | number | page size |
snapshot | QuerySnapshot | last successful snapshot/page fetched |
status | PagingStatus | status of pagination object |
PageForward | function | Changes the listener to the next page forward |
PageBack | function | Changes the listener to the next page backward |
new exports.PaginatedListener(table, filterArray, sortArray, refPath, limit, dataCallback, errCallback)
Param | Type | Default | Description |
---|---|---|---|
table | string | a properly formatted string representing the requested collection - always an ODD number of elements | |
filterArray | filterObject | an (optional) 3xn array of filter(i.e. "where") conditions | |
sortArray | sortObject | a 2xn array of sort (i.e. "orderBy") conditions | |
refPath | refPath | (optional) allows "table" parameter to reference a sub-collection of an existing document reference (I use a LOT of structered collections) The array is assumed to be sorted in the correct order - i.e. filterArray0 is added first; filterArraylength-1 last returns data as an array of objects (not dissimilar to Redux State objects) with both the documentID and documentReference added as fields. | |
limit | number | (optional) | |
dataCallback | callback | ||
errCallback | callback |
FirebaseFirestoreWrapper.PAGINATE_CHOICES : enum
Kind: static enum of FirebaseFirestoreWrapper
FirebaseFirestoreWrapper.documentId
- a fieldPath value to represent the document Id - WARNING Google Firestore has a bug, and this actually represents the FULL PATH to the document
Kind: static constant of FirebaseFirestoreWrapper
FirebaseFirestoreWrapper.deleteFieldValue
a sentinel value used to delete a field during an update operation
Kind: static constant of FirebaseFirestoreWrapper
FirebaseFirestoreWrapper.serverTimestampFieldValue
a sentinel value to set a field to a server-generated timestamp during set(0 or update())
Kind: static constant of FirebaseFirestoreWrapper
FirebaseFirestoreWrapper.MAX_CONCURRENCY : number
maximum concurrent writes
Kind: static constant of FirebaseFirestoreWrapper
FirebaseFirestoreWrapper.PAGINATE_INIT : number
Kind: static constant of FirebaseFirestoreWrapper
FirebaseFirestoreWrapper.PAGINATE_PENDING : number
Kind: static constant of FirebaseFirestoreWrapper
FirebaseFirestoreWrapper.PAGINATE_UPDATED : number
Kind: static constant of FirebaseFirestoreWrapper
FirebaseFirestoreWrapper.PAGINATE_DEFAULT : number
Kind: static constant of FirebaseFirestoreWrapper
FirebaseFirestoreWrapper.timestamp()
- Firestore timestamp processor
Kind: static method of FirebaseFirestoreWrapper
FirebaseFirestoreWrapper.incrementFieldValue(n) ⇒
Kind: static method of FirebaseFirestoreWrapper Returns: a sentinel value
Param | Description |
---|---|
n | If either the operand or the current field value uses floating point precision, all arithmetic follows IEEE 754 semantics. If both values are integers, values outside of JavaScript's safe number range (Number.MIN_SAFE_INTEGER to Number.MAX_SAFE_INTEGER) are also subject to precision loss. Furthermore, once processed by the Firestore backend, all integer operations are capped between -2^63 and 2^63-1. If the current field value is not of type number, or if the field does not yet exist, the transformation sets the field to the given value. |
FirebaseFirestoreWrapper.arrayRemoveFieldValue(elements) ⇒ sentinelValue
returns a sentinel to remove elements from array field
Kind: static method of FirebaseFirestoreWrapper Returns: sentinelValue - a sentinel value
Param | Description |
---|---|
elements | REST expanded list of elements to remove |
FirebaseFirestoreWrapper.arrayUnionFieldValue(elements) ⇒
return a sentinel to add/join elements to array field
Kind: static method of FirebaseFirestoreWrapper Returns: a sentinel value
Param | Description |
---|---|
elements | REST expanded list of elements to add |
FirebaseFirestoreWrapper.RecordFromSnapshot(Snap) ⇒ Record
returns an internal record structure from a firestore snapshot
Kind: static method of FirebaseFirestoreWrapper
Param | Type |
---|---|
Snap | Snapshot |
FirebaseFirestoreWrapper.DocumentFromRecord(Record) ⇒ object
returns a Firestore document structure from an internal Record
Kind: static method of FirebaseFirestoreWrapper
Param | Type | Description |
---|---|---|
Record | object | cleans up internal document representation |
FirebaseFirestoreWrapper.runTransaction(updateFunction) ⇒ Promise.<object>
creates and runs a series of record operations (executed in the param function) as an atomic operation. A transation object is passed to the callback parameter
Kind: static method of FirebaseFirestoreWrapper Returns: Promise.<object> - a promise with the result of updateFunction
Param | Type | Description |
---|---|---|
updateFunction | callback | callback function that expects a Transaction token as it's sole argument. either all the included/chained record operations will succeed, or none |
FirebaseFirestoreWrapper.openWriteBatch() ⇒ WriteBatch
Kind: static method of FirebaseFirestoreWrapper Returns: WriteBatch - object that operations are added to for a bulk operation Sync:
FirebaseFirestoreWrapper.closeWriteBatch(batch) ⇒ Promise.<void>
dispatches an asynchronous Closure to submit Batch
Kind: static method of FirebaseFirestoreWrapper
Param | Type | Description |
---|---|---|
batch | WriteBatch | WriteBatch to close |
FirebaseFirestoreWrapper.createUniqueReference(tablePath, refPath) ⇒ DocumentReference
adds a blank document to the collection referenced in parameter tablePath (relative to optional refPath) and returns it's reference. This is useful for Transactions and Batches, which can only get(), set() or update() existing documents. Tricksie!
Kind: static method of FirebaseFirestoreWrapper Returns: DocumentReference - Firestore Document Reference Sync:
Param | Description |
---|---|
tablePath | string representing a valid path to a collection to create the new document in, relative to a document reference passed in |
refPath | an optional valid document reference to start the table path |
FirebaseFirestoreWrapper.dbReference(refPath)
generates a document reference from a path if passed; else returns the db base reference
Kind: static method of FirebaseFirestoreWrapper Sync:
Param | Type | Description |
---|---|---|
refPath | string | Path to base actions from. May be null |
FirebaseFirestoreWrapper.writeRecord(tablePath, data, refPath, batch, mergeOption) ⇒ Promise.<Record>
writes a Firestore record to collection indicated by tablePath relative to option DocumentReference refPath
Kind: static method of FirebaseFirestoreWrapper
Param | Type | Description |
---|---|---|
tablePath | string | string representing a valid path to a collection to create or update the document in, relative to a document reference passed in |
data | Record | Data/Record object to write to database |
refPath | string | an optional valid document reference to start the table path |
batch | WriteBatch | Transaction | optional chain token to include this operation as part of an Atomic Transaction |
mergeOption | boolean | whether to merge into existin data; default TRUE |
FirebaseFirestoreWrapper.writeRecordByRefPath(data, refPath, Transaction, mergeOption) ⇒ Promise.<Record>
Writes given data object (or map) to the given documentReference
Kind: static method of FirebaseFirestoreWrapper Returns: Promise.<Record> - data record as written
Param | Type | Description |
---|---|---|
data | Record | Object/Map to be written back to the Firestore |
refPath | string | DocumentReference to write document to |
Transaction | WriteBatch | Transaction | Optional Transaction to enclose this action in |
mergeOption | boolean | whether to merge into existin data; default TRUE |
FirebaseFirestoreWrapper.writeBack(data, Transaction, mergeOption) ⇒ Promise.<Record>
Writes a local-schema document back to the Firestore. Assume object/map came from the firestore
Kind: static method of FirebaseFirestoreWrapper Returns: Promise.<Record> - record as written.
Param | Type | Description |
---|---|---|
data | Record | Object/Map to be written back to the Firestore |
Transaction | WriteBatch | Transaction | Optional Transaction to enclose this action in |
mergeOption | boolean | whether to merge into existin data; default TRUE |
FirebaseFirestoreWrapper.collectRecords(tablePath, refPath) ⇒ Promise.<Array.<Record>>
query for a SET of records
Kind: static method of FirebaseFirestoreWrapper
Param | Type | Description |
---|---|---|
tablePath | string | string representing path ro requested collection |
refPath | string | string representing a path to the relative PARENT of the requested collection |
FirebaseFirestoreWrapper.collectRecordsByFilter(table, filterArray, refPath) ⇒ Promise.<Array.<Record>>
Kind: static method of FirebaseFirestoreWrapper Descriptions: returns an array of documents from Firestore
Param | Type | Description |
---|---|---|
table | string | a properly formatted string representing the requested collection - always an ODD number of elements |
filterArray | filterObject | an array of filterObjects The array is assumed to be sorted in the correct order - i.e. filterArray0 is added first; filterArraylength-1 last returns data as an array of objects (not dissimilar to Redux State objects) with both the documentID and documentReference added as fields. |
refPath | string | (optional) allows "table" parameter to reference a sub-collection of an existing document reference (I use a LOT of structured collections) |
FirebaseFirestoreWrapper.collectRecordsInGroup(tableName) ⇒ Promise.<Array.<Record>>
query for a SET of records from a COLLECTIONGROUP - all collections of a similar name, regardless of parents. It is up to the User to ensure these are at a similar level/structure - Firestore just matches the name
Kind: static method of FirebaseFirestoreWrapper
Param | Type | Description |
---|---|---|
tableName | string | string describing the NAME of the collection group desired |
FirebaseFirestoreWrapper.collectRecordsInGroupByFilter(tableName, filterArray) ⇒ Promise.<Array.<Record>>
queries for Records from a CollectionGroup, filtered by the passed array of filterObjects
Kind: static method of FirebaseFirestoreWrapper
Param | Type | Description |
---|---|---|
tableName | string | string describing the Name of the collectiongroup |
filterArray | filterObject | array of objects describing filter operations |
FirebaseFirestoreWrapper.fetchRecord(tablePath, Id, refPath, batch) ⇒ Promise.<(Record|WriteBatch|Transaction)>
retrieve a record from the Firestore. If a Batch object is passed, returns a chained Btahc object
Kind: static method of FirebaseFirestoreWrapper
Param | Type | Description |
---|---|---|
tablePath | string | path to the enclosing collection |
Id | string | Id of the specific document requested |
refPath | string | optional document reference to base tablePath from |
batch | WriteBatch | Transaction | optional batch reference |
FirebaseFirestoreWrapper.fetchRecordByRefPath(docRefPath, batch) ⇒ Promise.<Record>
fetches a single record from the database, using just a refPath to identify the document
Kind: static method of FirebaseFirestoreWrapper
Param | Type | Description |
---|---|---|
docRefPath | string | string identifying the full path to the requested document |
batch | WriteBatch | Transaction | object for collecting batched operations |
FirebaseFirestoreWrapper.fetchRecordByFilter(table, filterArray, refPath, batch) ⇒ Promise.<(Record|WriteBatch|Transaction)>
fetches a SINGLE record from the database, using just a filter to identify the document. DANGEROUSLY assumes the filter identifies a SINGLE document, even if the query always returns an array
Kind: static method of FirebaseFirestoreWrapper
Param | Type | Description |
---|---|---|
table | string | a properly formatted string representing the requested collection - always an ODD number of elements |
filterArray | filterObject | array of objects describing filter operations |
refPath | string | optional document reference to base tablePath from |
batch | WriteBatch | Transaction | optional batch reference |
FirebaseFirestoreWrapper.fetchRecordInGroupByFilter(table, filterArray, batch) ⇒ Promise.<(Record|WriteBatch|Transaction)>
fetches a SINGLE record from the database, using just a filter to identify the document. DANGEROUSLY assumes the filter identifies a SINGLE document, even if the query always returns an array
Kind: static method of FirebaseFirestoreWrapper
Param | Type | Description |
---|---|---|
table | string | a properly formatted string representing the requested collection - always an ODD number of elements |
filterArray | filterObject | array of objects describing filter operations |
batch | WriteBatch | Transaction | optional batch reference |
FirebaseFirestoreWrapper.deleteRecord(table, record, refPath, batch) ⇒ Promise.<(Record|WriteBatch|Transaction)>
deletes a single record from the database
Kind: static method of FirebaseFirestoreWrapper
Param | Type | Description |
---|---|---|
table | string | string naming the parent collection of the document |
record | Record | |
refPath | string | optional document reference to base tablePath from |
batch | WriteBatch | Transaction | optional batch reference |
FirebaseFirestoreWrapper.deleteRecordByRefPath(docRefPath, batch) ⇒ Promise.<(Record|WriteBatch|Transaction)>
deletes a single record from the database
Kind: static method of FirebaseFirestoreWrapper
Param | Type | Description |
---|---|---|
docRefPath | string | string identifying the full path to the requested document |
batch | WriteBatch | Transaction | optional batch reference |
FirebaseFirestoreWrapper.updateRecordFields(recordUpdate) ⇒ Promise.<Record>
update record by fields - Allows use of FieldPath options such as .delete(). Only specifically referenced fields will be affected. Assumes the originating docRef is passed as refPath: field
Kind: static method of FirebaseFirestoreWrapper
Param | Type | Description |
---|---|---|
recordUpdate | Record | object of field:value entries to update. |
recordUpdate.refPath | string | full path to document/record |
FirebaseFirestoreWrapper.updateRecordByRefPath(docRefPath, data, batch) ⇒ Promise.<(Record|WriteBatch|Transaction)>
Kind: static method of FirebaseFirestoreWrapper
Param | Type | Description |
---|---|---|
docRefPath | string | full path to document to update |
data | Record | Record of values to update |
data.Id | string | document Id of record |
batch | WriteBatch | Transaction | batching object |
FirebaseFirestoreWrapper.writeArrayValue(fieldName, fieldValue, docRefPath, batch) ⇒ Promise.<(Record|WriteBatch|Transaction)>
adds a new value to a firestore record array entry
Kind: static method of FirebaseFirestoreWrapper
Param | Type | Description |
---|---|---|
fieldName | string | the string name of the array to be updated |
fieldValue | any | the value to add to the array |
docRefPath | string | the reference path for the document to be updated |
batch | WriteBatch | Transaction | optional - used to chain transactions |
FirebaseFirestoreWrapper.ListenRecords(tablePath, refPath, dataCallback, errCallback) ⇒ callback
sets up a listener for changes to a single record
Kind: static method of FirebaseFirestoreWrapper Returns: callback - function to be called to release subscription Sync:
Param | Type | Description |
---|---|---|
tablePath | string | string describing relative path to document |
refPath | string | string describing path to parent document |
dataCallback | QuerySnapshot | function to be called with changes to record |
errCallback | callback | function to be called when an error occurs in listener |
FirebaseFirestoreWrapper.ListenQuery(table, filterArray, sortArray, refPath, dataCallback, errCallback) ⇒ callback
Sets up a listener to a query
Kind: static method of FirebaseFirestoreWrapper Returns: callback - function to be called to release subscription Sync:
Param | Type | Description |
---|---|---|
table | string | Name of table to query too - may be sub-collection of optional reference |
filterArray | filterObject | a 3xn array of filter(i.e. "where") conditions |
sortArray | sortObject | an (optional) 2xn array of sort (i.e. "orderBy") conditions |
refPath | string | An optional Firestore DocumentReference. If present, the "table" parameter above is relative to this reference |
dataCallback | QuerySnapshot | callback function with query results |
errCallback | callback | callback function with error results |
FirebaseFirestoreWrapper.ListenCollectionGroupRecords(tablePath, refPath, dataCallback, errCallback) ⇒ callback
sets up a listener for changes to a collectionGroup
Kind: static method of FirebaseFirestoreWrapper Returns: callback - function to be called to release subscription Sync:
Param | Type | Description |
---|---|---|
tablePath | string | string describing relative path to document |
refPath | string | string describing path to parent document |
dataCallback | QuerySnapshot | function to be called with changes to record |
errCallback | callback | function to be called when an error occurs in listener |
FirebaseFirestoreWrapper.ListenCollectionGroupQuery(table, filterArray, sortArray, dataCallback, errCallback) ⇒ callback
sets up a listener for changes to a collectionGroup by query
Kind: static method of FirebaseFirestoreWrapper Returns: callback - function to be called to release subscription Sync:
Param | Type | Description |
---|---|---|
table | string | string describing the name of a collectionGroup |
filterArray | filterObject | a 3xn array of filter(i.e. "where") conditions |
sortArray | sortObject | an (optional) 2xn array of sort (i.e. "orderBy") conditions |
dataCallback | QuerySnapshot | function to be called with changes to record |
errCallback | callback | function to be called when an error occurs in listener |
FirebaseFirestoreWrapper.ListenRecord(tablePath, Id, refPath, dataCallback, errCallback) ⇒ callback
Listen to changes to a single record
Kind: static method of FirebaseFirestoreWrapper Returns: callback - function to be called to release subscription Sync:
Param | Type | Description |
---|---|---|
tablePath | string | string describing relative path to requested record |
Id | string | string of Id of requested document |
refPath | string | string od full path to parent document |
dataCallback | RecordListener | callback to handle changes to requested document |
errCallback | callback | callback to handle error reporting and operations |
FirebaseFirestoreWrapper.RecordListener : function
Kind: static typedef of FirebaseFirestoreWrapper
Param | Type |
---|---|
documentSnapshot | DocumentSnapshot |
FirebaseFirestoreWrapper.CollectionListener : function
Kind: static typedef of FirebaseFirestoreWrapper
Param | Type |
---|---|
querySnapshot | QuerySnapshot |
FirebaseFirestoreWrapper.PagingStatus : PAGINATE_INIT | PAGINATE_PENDING | PAGINATE_UPDATED | PAGINATE_DEFAULT
Kind: static typedef of FirebaseFirestoreWrapper
FirebaseFirestoreWrapper~initialize_firestore(firebase)
Initializes the Firestore service of the provided firebase app. Also instantiates various constants and helper functions
Kind: inner method of FirebaseFirestoreWrapper
Param | Type |
---|---|
firebase | firebase |
FirebaseFirestoreWrapper~Record : object
common properties of our database records
Kind: inner typedef of FirebaseFirestoreWrapper Properties
Name | Type | Description |
---|---|---|
Id | string | Id of the document as stored in Firestore May be null for new objects |
refPath | string | string representing the full path to the Firestore document. May be blank for new documents to be saved. |
FirebaseFirestoreWrapper~RecordArray : Record
an array of database records
Kind: inner typedef of FirebaseFirestoreWrapper
© 2020-2021 Tracy Hall