Ecosyste.ms: Awesome
An open API service indexing awesome lists of open source software.
https://github.com/roitinnovation/roit-data-firestore
https://github.com/roitinnovation/roit-data-firestore
Last synced: 29 days ago
JSON representation
- Host: GitHub
- URL: https://github.com/roitinnovation/roit-data-firestore
- Owner: roitinnovation
- License: mit
- Created: 2021-01-03T20:55:08.000Z (almost 4 years ago)
- Default Branch: master
- Last Pushed: 2024-03-17T01:39:00.000Z (9 months ago)
- Last Synced: 2024-05-17T03:43:36.875Z (7 months ago)
- Language: TypeScript
- Size: 784 KB
- Stars: 1
- Watchers: 2
- Forks: 6
- Open Issues: 2
-
Metadata Files:
- Readme: README.md
- License: LICENSE
Awesome Lists containing this project
README
# ROIT Data Firestore
Connect to firestore in a very easy and standardized way, using typescript and optionally [NestJs](https://docs.nestjs.com/)## Usage Simple Example
#### Model Class
To validate the model use [class-validator](https://www.npmjs.com/package/class-validator), in execute create or update operation the rules will be validated
```
export class User {
@IsString()
id: string@IsString()
@IsNotEmpty()
name: string@IsNumber()
age: number
}
```
#### Repository Class```
import { Query } from "@roit/roit-data-firestore";
import { Repository } from "@roit/roit-data-firestore";
import { BaseRepository } from "@roit/roit-data-firestore";
import { User } from "./model/User";
import { Paging } from "@roit/roit-data-firestore";@Repository({
collection: 'fb-data-test',
validateModel: User
})
// If use nest @Injectable()
export class Repository1 extends BaseRepository {
@Query()
findByName: (name: string) => Promise>@Query({ oneRow: true })
findByNameAndAge: (name: string, age: number, paging?: Paging) => Promise@Query()
findByNameAndAgeAndOrderByIdDesc: (name: string, age: number) => Promise>@Query({ select: ['name', 'age'] })
findByAge: (age: number) => Promise>
}
```
## Decorators
import { Repository, Query, Cacheable } from "@roit/roit-data-firestore";#### @Repository
The anotation Repository is responsible for register context from operator
```
@Repository({
collection: 'collection', // Firestore collection name
validateModel: Model // ref model from validate
})
```
#### @Query
The anotation Query is responsible from invoker the dynamic query creator and initialize implementation```
@Query()
findByName: (name: string) => Promise>
```#### @Cacheable
The anotation Cacheable is responsible from handler storage data in cache, local or using provider```
@Cacheable({
excludesMethods: [ // Excludes methods not to store data (optional, default [])
'findById'
],
cacheOnlyContainResults: true, // Cache data only query return value (optional, default true)
cacheProvider: CacheProviders.LOCAL, // REDIS or LOCAL (optional, default 'Local')
includeOnlyMethods: [] // Includes only the methods that will be stored (optional, default []),
cacheExpiresInSeconds: 60 // Cache expiration in seconds
})
```##### Cache environment variables
| Environment variable | Description | Default value |
| -------------------|------------------------------------| ---------------------------------------- |
| firestore.cache.redisUrl | Ex: redis://localhost:63279 |
| firestore.cache.timeout | Timeout to Redis response (ms) | 2000 |
| firestore.cache.reconnectInSecondsAfterTimeout | Time to try to reconnect after Redis timeout (s) | 30 |
| firestore.debug | Toggle debugging logs | false |## BaseRepository and ReadonlyRepository
To standardize the BaseRepository already provides the common methods for implementationimport { BaseRepository, Query, ReadonlyRepository } from "@roit/roit-data-firestore";
```
export abstract class BaseRepository {@Query()
findAll: (paging?: Paging) => Promise@Query()
findById: (id: Required) => Promise@Query()
create: (item: T | Array) => Promise>@Query()
update: (items: T | Array) => Promise>@Query()
createOrUpdate: (items: T | Array) => Promise>@Query()
updatePartial: (id: Required, itemPartial: Partial) => Promise@Query()
delete: (id: Required | Array) => Promise>@Query()
incrementField: (id: Required, field: Required, increment?: number) => Promise
}When you only need to read a collection, use ReadonlyRepository
export abstract class ReadonlyRepository {
@Query()
findAll: (paging?: Paging) => Promise@Query()
findById: (id: string) => Promise | undefined
}```
## Dynamic query contractor
The dynamic construction of a query allows a method to be described in a standardized way and the library dynamically creates the concrete implementationRef: [Firstore Operators](https://firebase.google.com/docs/firestore/query-data/queries)
#### Supported keywords inside method
| Keyword | Sample | Query |
| -------------------|------------------------------------| ---------------------------------------- |
| Iqual | findByLastNameIqual or findByLastName | .where('lastName', '==', value) |
| LessThan | findByAgeLessThan | .where('age', '<', value) |
| LessThanEqual | findByMonthLessThanEqual | .where('month', '<=', value) |
| GreaterThan | findByAgeUserGreaterThan | .where('ageUser', '>', value) |
| GreaterThanEqual | findByAgeAppleGreaterThanEqual | .where('ageApple', '>=', value) |
| Different | findByLastNameDifferent | .where('lastName', '!=', value) |
| ArrayContains | findByCitysArrayContains | .where('citys', 'array-contains', value) |
| ArrayContainsAny | findByCitysArrayContainsAny | .where('citys', 'array-contains-any', value) |
| In | findByCitysIn | .where('citys', 'in', value) |
| NotIn | findByFrangosNotIn | .where('frangos', 'not-in', value) |
| OrderBy Desc | findByNameAndOrderByNameDesc | .where('name', '==', value).orderBy("name", "desc")|
| OrderBy Asc | findByNameAndOrderByNameAsc | .where('name', '==', value).orderBy("name", "asc")|
| Limit | findByNameAndLimit10 | .where('name', '==', value).limit(10) |#### Example
```
@Query()
// When called example findByName('anyUser') result in query .where('name', '==', 'anyUser')
findByName: (name: string) => Promise>@Query()
// When called example findByNameAndAge('anyUser', 15) result in query .where('name', '==', 'anyUser').where('age', '==', 15)
findByNameAndAge: (name: string, age: number) => Promise>@Query()
// When called example findByNameAndAgeAndOrderByIdDesc('anyUser', 15) result in query .where('name', '==', 'anyUser').where('age', '==', 15).orderBy("id", "desc")
findByNameAndAgeAndOrderByIdDesc: (name: string, age: number) => Promise>
```## Paging support
For any query it is possible to pass the paging information
Paging option
```
orderBy?: string = 'id'orderByDirection?: Direction = 'asc'
cursor?: string | null = null
limit: number = 1000
```
#### Example
```
Any query@Query()
findByNameAndAge: (name: string, age: number) => Promise>Any query with paging
@Query()
findByNameAndAge: (name: string, age: number, paging?: Paging) => Promise>```
#### Manual Query
```
Use query() method preset in BaseRepository
findByNameAndId(name: string, id: string): Promise> {
return this.query([
{
field: 'name',
operator: '==',
value: name
},
{
field: 'id',
operator: '==',
value: id
}
])
}OR
findByNameAndId2(name: string, id: string): Promise> {
return this.query([{ name }, { id }])
}Full example
export class Repository1 extends BaseRepository {
@Query()
findByName: (name: string) => Promise>@Query()
findByNameAndAge: (name: string, age: number, paging?: Paging) => Promise>@Query()
findByNameAndAgeAndOrderByIdDesc: (name: string, age: number) => Promise>findByNameAndId(name: string, id: string): Promise> {
return this.query([
{
field: 'name',
operator: '==',
value: name
},
{
field: 'id',
operator: '==',
value: id
}
])
}findByNameAndId2(name: string, id: string): Promise> {
return this.query([{ name }, { id }])
}
}
```#### Paginated Query
```
Use queryPaginated() method preset in BaseRepository
findByNameAndId(name: string, id: string, paging: Paging): Promise> {
return this.queryPaginated({
query: [
{
field: 'name',
operator: '==',
value: name
},
{
field: 'id',
operator: '==',
value: id
}
],
paging
})
}The return of this method is a QueryResult:
class QueryResult {
data: T[];
totalItens: number | null;
}```
#### Select Example
```
@Query({ select: ['name', 'id'] })
findByName: (name: string) => Promise>findByNameAndId(name: string, id: string): Promise> {
return this.query({
query:[{name}, {id}],
select: ['name']
})
}
```## Firestore read auditing with Big Query
GCP Firestore does not provide a way to visualize the number of reads per collection, so with this functionality it is possible to save all the reads of a Firestore collection into a BigQuery table or dispatch to a PubSub topic for further analysis.
Example (using env.yaml):
```
firestore:
projectId: 'gcp-project-id'
audit:
enable: true
endAt: '2023-01-19 15:02:10' (optional - after this date, the audit will stop)
provider: 'PubSub' // PubSub or BigQuery (PubSub is the default option)
pubSubTopic: 'your-topic'
```## TTL Option
Firestore supports automatic data cleaning
```
@Repository({
collection: `collection`,
validateModel: Model,
ttl: {
expirationIn: 3,
unit: 'days',
ttlUpdate: false
}
})in data document there is create attribute ttlExpirationAt
```