Code Monkey home page Code Monkey logo

fireorm24's Introduction

πŸ”₯ FireORM24 πŸ”₯

NPM Version Build Status Typescript lang All Contributors License

Introduction

FireORM24 is a lightweight wrapper for firebase-admin designed to simplify interactions with Firestore databases. By abstracting the access layer and offering a familiar repository pattern, FireORM24 streamlines the development process for applications that use Firestore, allowing developers to concentrate on creating new features without dealing with the intricacies of Firestore.

Willy Ovalle (GH Profile), the original maintainer, stepped down from active support in March 2023. Since then, the project has been maintained through community contributions. The current effort, under the name FireORM24, focuses on updating the project with the latest security patches and new features to align with the latest Firebase advancements. The original project can be found here.

For more information on the motivations behind the project, you can read Willy's original introductory post on Medium. The API documentation is also available.

Usage

  1. Install the npm package:
yarn add fireorm24 reflect-metadata #or npm install fireorm24 reflect-metadata

# note: the reflect-metadata shim is required
  1. Initialize your Firestore application:
import { initializeApp } from "firebase/app";
import { getFirestore } from "firebase/firestore";
import serviceAccount from "../firestore.creds.json"; // Adjust path as necessary

const firebaseConfig = {
    apiKey: "YOUR_API_KEY",
    authDomain: "YOUR_PROJECT_ID.firebaseapp.com",
    projectId: "YOUR_PROJECT_ID",
    storageBucket: "YOUR_PROJECT_ID.appspot.com",
    messagingSenderId: "YOUR_MESSAGING_SENDER_ID",
    appId: "YOUR_APP_ID",
};

// Initialize Firebase
const app = initializeApp(firebaseConfig);

// Initialize Cloud Firestore and get a reference to the service
const db = getFirestore(app);
fireorm.initialize(db);
  1. Create your Firestore models:
import { Collection } from 'fireorm24';

@Collection()
class Programmer {
  id: string;
  name: string;
  language: string;
}
  1. Manage your Firestore data with ease!
import { Collection, getRepository } from 'fireorm24';

@Collection()
class Programmer {
  id: string;
  name: string;
  language: string;
}

const programmerRepository = getRepository(Programmer);

const willy = new Programmer();
willy.name = "Willy Ovale";
willy.language = "Typescript";

const programmerDocument = await programmerRepository.create(willy); // Create programmer

const mySuperProgrammerDocument = await programmerRepository.findById(programmerDocument.id); // Read programmer

mySuperProgrammerDocument.language = "Typescript, .NET";
await programmerRepository.update(mySuperProgrammerDocument); // Update programmer

await programmerRepository.delete(mySuperProgrammerDocument.id); // Delete programmer

Firebase Complex Data Types

Firestore supports complex data types such as GeoPoint and Reference. The integration of full support for these data types into the new FireORM project is currently in progress. This section will be updated with detailed information once the support is fully implemented.

Current Workaround

In the meantime, you can utilize Class Transformer's @Type decorator for handling nested objects. For example, refer to the GeoPoint Example.

Limitations

Currently, casting GeoPoints to a custom class requires latitude: number and longitude: number as public class fields. This limitation will be addressed in future updates.

Development

Initial Setup

  1. Clone the project from github:
git clone https://github.com/elersong/fireorm24.git
  1. Install the dependencies:
yarn # npm install

Testing

Fireorm has two types of tests:

  • Unit tests: yarn test # or npm test
  • Integration tests: yarn test:integration # or npm test:integration

To be able to run the integration tests you need to create a Firebase service account and declare some environment variables.

Test files must follow the naming convention *.test.ts and use jest as the test runner.

Committing

This repo uses Conventional Commits as the commit messages convention.

Release a new version

This repo uses Semantic Release to automatically release new versions as soon as they land on master.

Commits must follow Angular's Git Commit Guidelines.

Supported commit types (taken from here):

  • feat: A new feature
  • fix: A bug fix
  • docs: Documentation only changes
  • style: Changes that do not affect the meaning of the code (white-space, formatting, missing semi-colons, etc)
  • refactor: A code change that neither fixes a bug nor adds a feature
  • perf: A code change that improves performance
  • test: Adding missing or correcting existing tests
  • chore: Changes to the build process or auxiliary tools and libraries such as documentation generation
Manual Release If, by any reason, a manual release must be done, these are the instructions:
  • To release a new version to npm, first we have to create a new tag:
npm version [ major | minor | patch ] -m "Relasing version"
git push --follow-tags
  • Then we can publish the package to npm registry:
npm publish
  • To deploy the documentation:
yarn deploy:doc # or npm deploy:doc

Documentation

  • Fireorm uses typedoc to automatically build the API documentation, to generate it:
yarn build:doc # or npm build:doc

Documentation is automatically deployed on each commit to master and is hosted in Github Pages in this link.

Contributing

Have a bug or a feature request? Please visit the new issues page on the FireORM24 repository. To contribute, check the new issues page to find a problem you would like to solve. Additionally, you can review the old FireORM issues page to see if any requested features have not yet been reported to the new repository.

Pull requests are welcome!

Contributors

Thanks goes to these wonderful people (emoji key):


Willy Ovalle

πŸ’» πŸ“– πŸ’‘ πŸ€” πŸ‘€ ⚠️

Maximo Dominguez

πŸ€” πŸ’»

Nathan Jones

πŸ’»

Sergii Kalashnyk

πŸ’»

SaltyKawaiiNeko

πŸ’» πŸ€”

z-hirschtritt

πŸ’» πŸ€”

Joe McKie

πŸ’» πŸ€”

Samed Düzçay

πŸ’»

stefdelec

πŸ’»

Łukasz Kuciel

πŸ’»

Yaroslav Nekryach

πŸ’»

Dmytro Nikitiuk

πŸ’»

JingWangTW

πŸ’»

Rink Stiekema

πŸ’»

Daniel

πŸ’»

Marko Zabreznik

πŸ’»

Jose Mendez

πŸ’»

Grey Elerson

πŸ’» πŸ€”

This project follows the all-contributors specification. Contributions of any kind welcome!

License

MIT Β© Willy Ovalle and Grey Elerson. See LICENSE for details.

fireorm24's People

Contributors

wovalle avatar elersong avatar allcontributors[bot] avatar joemckie avatar jonesnc avatar rinkstiekema avatar zhirschtritt avatar mamodom avatar zdafs avatar skneko avatar dependabot[bot] avatar ppicom avatar danieleisenhardt avatar leoafarias avatar marzab avatar jingwangtw avatar smddzcy avatar skalashnyk avatar braaar avatar erick2280 avatar jomendez avatar kronhyx avatar sgmonda avatar talesmgodois avatar joaomilho avatar gregfenton avatar yaroslavnekryach avatar lukaszkuciel avatar

Stargazers

Rafael Gongora Bariccatti avatar

fireorm24's Issues

Implement Validate Decorator for Model Validation

Description

Fireorm supports validation by leveraging class-validator. The current implementation requires validation to be manually checked in each repository function. To improve code maintainability and readability, a Validate decorator should be created to handle this validation automatically.

Steps to Reproduce

  1. Manually add validation checks in repository functions.
  2. Notice the repeated validation code and the need for a more elegant solution.

Expected Behavior

A Validate decorator should handle model validation, reducing code repetition and improving maintainability.

Actual Behavior

Validation checks are manually implemented in repository functions, leading to repeated code and potential for errors.

Acceptance Criteria

  • Create a Validate decorator that checks if validation should be performed and returns a ValidationError.
  • Create unit tests to ensure the decorator functions correctly.
  • Replace the existing validation code with the new decorator.
  • Ensure each function that requires validation uses the Validate decorator.

Additional Context

  • October 17, 2020: Initial proposal to implement a Validate decorator.
  • February 23, 2021: Mention of a feature to optionally allow validatorOptions of class-validator.

Proposed API Changes

  1. Create Validate Decorator:

    • Develop a Validate decorator to handle model validation.
    import { validate } from 'class-validator';
    import { ValidationError } from 'class-validator';
    
    function Validate(target: any, propertyName: string, descriptor: TypedPropertyDescriptor<Function>) {
      const method = descriptor.value;
    
      descriptor.value = async function (...args: any[]) {
        if (this.config.validateModels) {
          const errors = await validate(args[0]);
          
          if (errors.length) {
            throw new ValidationError(errors);
          }
        }
    
        return method.apply(this, args);
      };
    }
  2. Unit Tests:

    • Create unit tests to validate the functionality of the Validate decorator.
    import { Validate } from './validate.decorator';
    
    class TestClass {
      @Validate
      async someMethod(item: any) {
        // method logic
      }
    }
    
    test('should throw validation errors', async () => {
      const instance = new TestClass();
      instance.config = { validateModels: true };
    
      await expect(instance.someMethod({})).rejects.toThrow(ValidationError);
    });
  3. Replace Existing Validation Code:

    • Refactor existing repository functions to use the Validate decorator instead of manual validation checks.
  4. Ensure Usage of Validate Decorator:

    • Ensure all functions requiring validation are decorated with @Validate.

Example Implementation

@Collection()
class User {
  @IsString()
  id: string;

  @IsString()
  display_name: string;

  @IsString()
  username: string;
}

// Repository instance
class UserRepository {
  @Validate
  async create(user: User) {
    // create logic
  }

  @Validate
  async update(user: User) {
    // update logic
  }
}

Original Issue

Compatibility Issue with Timestamps in firebase-admin SDK v11.11.1

Description

The update method of BaseFirestoreRepository in Fireorm is not handling Timestamp objects created with the latest firebase-admin SDK v11.11.1. The Timestamp objects from firebase-admin v11.11.1 are not recognized as valid Firestore documents, whereas Timestamp objects from @google-cloud/firestore still work as expected.

Steps to Reproduce

  1. Create an entity with a timestamp field.
  2. Use firebase-admin SDK v11.11.1 to update the entity's timestamp field.
  3. Attempt to update the entity in Firestore using Fireorm.
  4. Observe the error related to the Timestamp object.

Expected Behavior

Fireorm should be able to handle Timestamp objects from the latest firebase-admin SDK without errors.

Actual Behavior

An error occurs, indicating that the Timestamp object is not a valid Firestore document.

Acceptance Criteria

  • Ensure compatibility with Timestamp objects from firebase-admin SDK v11.11.1.
  • Ensure backward compatibility with previous versions of firebase-admin and @google-cloud/firestore.
  • Add unit tests to validate the handling of Timestamp objects from different SDKs.

Additional Context

  • December 9, 2023: Initial issue raised about compatibility problems with firebase-admin SDK v11.11.1.
  • April 12, 2023: Similar issues reported with different projects using firebase-admin and @google-cloud/firestore.

Proposed API Changes

  1. Update Timestamp Handling:

    • Modify the update method to handle Timestamp objects from both firebase-admin and @google-cloud/firestore.
    import { firestore } from 'firebase-admin';
    import { Timestamp as GCTimestamp } from '@google-cloud/firestore';
    
    class BaseFirestoreRepository<T> {
      async update(item: T): Promise<T> {
        const plainObject = this.convertToPlainObject(item);
        await this.firestoreColRef.doc(item.id).update(plainObject);
        return item;
      }
    
      private convertToPlainObject(item: T): any {
        const obj = JSON.parse(JSON.stringify(item));
        for (const key in obj) {
          if (obj[key] instanceof firestore.Timestamp || obj[key] instanceof GCTimestamp) {
            obj[key] = obj[key].toDate();
          }
        }
        return obj;
      }
    }
  2. Unit Tests:

    • Add unit tests to validate the handling of Timestamp objects from different SDKs.
    import { firestore } from 'firebase-admin';
    import { Timestamp as GCTimestamp } from '@google-cloud/firestore';
    
    test('should handle firebase-admin Timestamp', async () => {
      const repo = getRepository(MyEntity);
      const entity = new MyEntity();
      entity.id = 'entity1';
      entity.timestamp = firestore.Timestamp.fromDate(new Date());
    
      await expect(repo.update(entity)).resolves.not.toThrow();
    });
    
    test('should handle @google-cloud/firestore Timestamp', async () => {
      const repo = getRepository(MyEntity);
      const entity = new MyEntity();
      entity.id = 'entity1';
      entity.timestamp = GCTimestamp.fromDate(new Date());
    
      await expect(repo.update(entity)).resolves.not.toThrow();
    });

Example Implementation

import { Collection, getRepository } from 'fireorm';
import { firestore } from 'firebase-admin';
import { Timestamp as GCTimestamp } from '@google-cloud/firestore';

@Collection()
class MyEntity {
  id: string;
  timestamp: Date;
}

const repo = getRepository(MyEntity);

async function updateEntityWithAdminTimestamp() {
  const entity = await repo.findById('entity1');
  entity.timestamp = firestore.Timestamp.fromDate(new Date());
  await repo.update(entity);
}

async function updateEntityWithGCTimestamp() {
  const entity = await repo.findById('entity1');
  entity.timestamp = GCTimestamp.fromDate(new Date());
  await repo.update(entity);
}

updateEntityWithAdminTimestamp();
updateEntityWithGCTimestamp();

Original Issue

Expose Timestamps from DocumentSnapshot

Description

Firestore's DocumentSnapshot exposes timestamps such as createTime, readTime, and updateTime. It would be beneficial to expose these timestamps in Fireorm entities, allowing users to access metadata about document operations.

Steps to Reproduce

  1. Retrieve a document using Fireorm.
  2. Attempt to access timestamps like createTime, readTime, and updateTime.

Expected Behavior

Ability to access createTime, readTime, and updateTime directly from Fireorm entities.

Actual Behavior

Currently, these timestamps are not exposed in Fireorm entities.

Acceptance Criteria

  • Implement a mechanism to expose createTime, readTime, and updateTime in Fireorm entities.
  • Ensure the implementation is clean and does not require adding arbitrary fields to entities unless needed.

Additional Context

  • May 3, 2020: Initial issue raised to expose timestamps from DocumentSnapshot.
  • May 10, 2020: Discussion on the best approach to implement this feature, considering the use of decorators or Symbols for a clean implementation.

Proposed API Changes

  1. Use of Decorators:

    • Introduce decorators to map entity fields to autogenerated or readonly fields in Firestore.
    • Example:
      @Collection()
      class MyEntity {
        id: string;
        
        @CreatedOnField()
        createTime: FirebaseFirestore.Timestamp;
        
        @ReadOnField()
        readTime: FirebaseFirestore.Timestamp;
        
        @UpdatedOnField()
        updateTime: FirebaseFirestore.Timestamp;
      }
  2. Use of Symbols:

    • Expose Symbols that developers can use to override entity fields for metadata fields.
    • Example:
      import { Symbols } from 'fireorm';
      
      @Collection()
      class MyEntity {
        id: string;
        
        [Symbols.createTime]: FirebaseFirestore.Timestamp;
        [Symbols.readTime]: FirebaseFirestore.Timestamp;
        [Symbols.updateTime]: FirebaseFirestore.Timestamp;
      }
  3. Testing and Validation:

    • Ensure thorough testing to validate that the timestamps are correctly mapped and accessible.
    • Verify that the implementation does not interfere with existing functionalities and remains backward compatible.

Original Issue

Enable strictNullChecks for Better Type Safety

Description

The findById method in BaseFirestoreRepository is expected to return Promise<T | null>. However, the generated TypeScript declaration file shows Promise<T>, losing the benefit of type safety. This issue is likely due to strictNullChecks being set to false. Enabling strictNullChecks and fixing the resulting errors will improve type safety throughout the library.

Steps to Reproduce

  1. Check the findById method in BaseFirestoreRepository.
  2. Observe that it is expected to return Promise<T | null>.
  3. Build the library and check the generated declaration file.
  4. Notice that the return type is Promise<T> instead of Promise<T | null>.

Expected Behavior

The findById method should correctly reflect Promise<T | null> in the generated declaration file, ensuring proper type safety.

Actual Behavior

The findById method's return type is Promise<T> in the generated declaration file, losing the benefit of type safety.

Acceptance Criteria

  • Enable strictNullChecks in the TypeScript configuration.
  • Fix any resulting errors to ensure the library builds successfully.
  • Ensure the correct return types are reflected in the generated declaration files.

Additional Context

  • March 18, 2022: Initial issue raised about the incorrect return type in the generated declaration file.
  • April 7, 2022: Acknowledgment of the issue and the need for help to fix it.

Proposed API Changes

  1. Enable strictNullChecks:

    • Update the tsconfig.json file to enable strictNullChecks.
    {
      "compilerOptions": {
        "strictNullChecks": true,
        // Other compiler options...
      }
    }
  2. Fix Resulting Errors:

    • Address the errors that occur when enabling strictNullChecks to ensure the library builds successfully.
    // Example fix for the findById method
    async findById(id: string): Promise<T | null> {
      return this.firestoreColRef
        .doc(id)
        .get()
        .then(d => (d.exists ? this.extractTFromDocSnap(d) : null));
    }
  3. Validate Return Types:

    • Ensure the correct return types are reflected in the generated declaration files.
    // Check the generated declaration file
    declare class BaseFirestoreRepository<T> {
      findById(id: string): Promise<T | null>;
    }
  4. Unit Tests:

    • Add unit tests to validate the correct return types and behavior of methods.
    test('findById should return null when document does not exist', async () => {
      const repo = getRepository(MyEntity);
      const result = await repo.findById('nonexistent-id');
      expect(result).toBeNull();
    });
    
    test('findById should return entity when document exists', async () => {
      const repo = getRepository(MyEntity);
      const entity = new MyEntity();
      entity.id = 'existent-id';
      await repo.create(entity);
    
      const result = await repo.findById('existent-id');
      expect(result).toEqual(entity);
    });

Example Implementation

class MyEntity {
  id: string;
}

@Collection("myEntities")
class MyEntityRepository extends BaseFirestoreRepository<MyEntity> {}

const repo = getRepository(MyEntity);

const entity = new MyEntity();
entity.id = "existent-id";
await repo.create(entity);

const result = await repo.findById("existent-id");
console.log(result); // Output: MyEntity instance or null

Original Issue

Support for Constructor Parameters in Collection Classes

Description

FireORM currently requires collection classes to have parameterless constructors to initialize instances. This limitation forces developers to add undefined and null types to all class attributes to satisfy TypeScript's strict mode, leading to potential issues with data storage and unnecessary null values being written to the datastore.

Steps to Reproduce

  1. Create a collection class with attributes.
  2. Add a parameterless constructor to comply with FireORM requirements.
  3. Attempt to initialize the class with data, encountering TypeScript errors if attributes are not undefined or null.

Expected Behavior

Allow collection classes to have constructors with parameters to initialize data while still supporting FireORM's initialization process. Ensure undefined values are not written to the datastore to avoid storage issues and maintain data integrity.

Actual Behavior

Developers must add undefined and null types to attributes, leading to unnecessary null values in the datastore.

Acceptance Criteria

  • Allow collection classes to have constructors with parameters, handling undefined parameters when invoked by FireORM.
  • Ensure undefined values are not written to the datastore.
  • Maintain compatibility with TypeScript's strict mode without requiring additional types for attributes.

Additional Context

  • November 11, 2019: Initial issue raised about the limitation of parameterless constructors.
  • November 19, 2019: Discussion about the challenges of overloading constructors in JavaScript/TypeScript.
  • November 28, 2019: Suggestions for workarounds using factory methods or TypeScript-specific constructor overloading.
  • April 22, 2020: Referenced in issues about type assignability and strict property initialization.
  • November 13, 2020: Mentioned in a feature request to add constructor support to the Collection decorator.

Original Issue

Enable Stricter TS Rules

Description

To improve code quality and catch potential issues early, Fireorm should enable stricter TypeScript rules. The goal is to gradually migrate the codebase to comply with strict: true settings in tsconfig.json.

Steps to Reproduce

  1. Enable strict TypeScript rules in tsconfig.json.
  2. Attempt to compile the Fireorm codebase.
  3. Identify and fix TypeScript errors that arise from stricter type checking.

Expected Behavior

The Fireorm codebase should compile without errors under strict TypeScript rules, ensuring better type safety and code quality.

Actual Behavior

The current codebase may not compile under strict TypeScript rules due to various type-related issues.

Acceptance Criteria

  • Enable strict TypeScript rules incrementally, addressing errors on a folder-by-folder or file-by-file basis.
  • Ensure the entire codebase eventually complies with strict: true settings.
  • Improve overall type safety and code quality in Fireorm.

Additional Context

  • July 4, 2020: Initial interest in tackling the stricter TypeScript rules.
  • July 5, 2020: Agreement on the need for stricter rules and the scope of the task.
  • July 11, 2020: Suggestion to take an incremental approach to the migration.
  • July 12, 2020: Ongoing work on a PR to address many existing errors.
  • July 18, 2020: PR merged to address a significant number of errors.
  • July 31, 2020: Initial part of strict rules migration merged.
  • August 17, 2020: Plan to address strict rules on a folder-by-folder basis with a detailed list of pending work.

Proposed Plan

  1. Incremental Approach:

    • Migrate to strict TypeScript rules incrementally, focusing on one folder or file at a time.
    • Use an inner tsconfig.json to enable strict rules for specific folders or files and fix the errors.
  2. Folder-by-Folder Migration:

    • Address the following folders and files in sequence:
      • src/Batch/
      • src/Errors/
      • src/Transaction/
      • src/AbstractFirestoreRepository.ts
      • src/BaseFirestoreRepository.ts
      • src/BaseRepository.ts
      • src/helpers.ts
      • src/MetadataStorage.ts
      • src/QueryBuilder.ts
      • src/utils.ts
  3. Final Integration:

    • After migrating all folders and files, remove the inner tsconfig.json.
    • Enable strict: true in the outer tsconfig.json.

Example Implementation

// Update tsconfig.json to enable strict rules
{
  "compilerOptions": {
    // other options...
    "strict": true
  },
  "include": [
    "src/**/*.ts"
  ]
}

// Fix TypeScript errors in migrated files
export class ExampleClass {
  // Ensure all properties are properly typed
  private exampleProperty: string;

  constructor(exampleProperty: string) {
    this.exampleProperty = exampleProperty;
  }
}

Original Issue

Refactor Error Handling

Description

Fireorm can throw different types of errors, including Firestore errors, validation errors, and other generic errors. Currently, error declarations are scattered throughout the codebase, leading to duplicated and inconsistent error handling. To improve maintainability and readability, error handling should be centralized.

Steps to Reproduce

  1. Trigger different types of errors in Fireorm.
  2. Observe the scattered and duplicated error declarations.

Expected Behavior

Centralized error handling with consistent error messages and types, reducing code duplication and improving maintainability.

Actual Behavior

Error declarations are scattered throughout the codebase, leading to duplicated and inconsistent error handling.

Acceptance Criteria

  • Refactor new Error() instances in the codebase by creating centralized error definitions in src/Errors.
  • Remove duplicated error declarations.
  • Ensure consistent error handling throughout the codebase.

Additional Context

  • October 17, 2020: Initial proposal to refactor error handling in Fireorm.

Proposed API Changes

  1. Centralize Error Definitions:

    • Create a centralized location for error definitions in src/Errors.
    // src/Errors/index.ts
    export class FirestoreError extends Error {
      constructor(message: string) {
        super(message);
        this.name = 'FirestoreError';
      }
    }
    
    export class ValidationError extends Error {
      constructor(message: string) {
        super(message);
        this.name = 'ValidationError';
      }
    }
    
    export class GenericError extends Error {
      constructor(message: string) {
        super(message);
        this.name = 'GenericError';
      }
    }
  2. Refactor Error Handling in Codebase:

    • Replace all instances of new Error() with the appropriate custom error classes.
    import { FirestoreError, ValidationError, GenericError } from './Errors';
    
    // Example refactoring
    try {
      // some Firestore operation
    } catch (error) {
      throw new FirestoreError('Failed to perform Firestore operation');
    }
    
    if (this.config.validateModels) {
      const errors = await validate(item);
      if (errors.length) {
        throw new ValidationError('Model validation failed');
      }
    }
    
    if (someOtherCondition) {
      throw new GenericError('An unknown error occurred');
    }
  3. Remove Duplicated Error Declarations:

    • Identify and remove any duplicated error declarations throughout the codebase.

Example Implementation

@Collection()
class User {
  @IsString()
  id: string;

  @IsString()
  display_name: string;

  @IsString()
  username: string;
}

// Repository instance
class UserRepository {
  async create(user: User) {
    try {
      // create logic
    } catch (error) {
      throw new FirestoreError('Failed to create user');
    }
  }

  async update(user: User) {
    try {
      // update logic
    } catch (error) {
      throw new FirestoreError('Failed to update user');
    }
  }
}

Original Issue

Ensure Subcollections Use TransactionRepositories Inside Transactions

Description

When interacting with subcollections inside a transaction, the subcollections should also be TransactionRepositories to ensure consistency and proper transaction handling. Currently, subcollections do not automatically switch to TransactionRepositories within transactions.

Steps to Reproduce

  1. Start a transaction and interact with a subcollection.
  2. Observe that the subcollection is not treated as a TransactionRepository.

Expected Behavior

Subcollections should automatically switch to TransactionRepositories when inside a transaction.

Actual Behavior

Subcollections remain as regular repositories, not switching to TransactionRepositories within transactions.

Acceptance Criteria

  • Change the type of ISubCollection<Entity> to ITransactionRepository<Entity> when interacting with subcollections inside a transaction.
  • Ensure that all subcollection interactions within a transaction use the TransactionRepository.
  • Add unit tests to validate the behavior of subcollections within transactions.

Additional Context

  • June 10, 2022: Initial issue raised about the need for subcollections to use TransactionRepositories inside transactions.
  • June 17, 2022: Agreement on the need for the change and a request for a pull request.

Proposed API Changes

  1. Update ISubCollection Type:

    • Change the type of ISubCollection<Entity> to ITransactionRepository<Entity> when inside a transaction.
    interface ISubCollection<Entity> {
      // Existing methods...
    }
    
    interface ITransactionRepository<Entity> extends ISubCollection<Entity> {
      // Additional methods specific to transactions...
    }
  2. Modify Repository Initialization:

    • Ensure that subcollections are initialized as TransactionRepositories when inside a transaction.
    class BaseFirestoreRepository<T> {
      async runTransaction<R>(fn: (tran: Transaction) => Promise<R>): Promise<R> {
        return this.firestore.runTransaction(async tran => {
          const transactionRepository = new TransactionRepository(this.firestoreColRef, tran);
          return await fn(transactionRepository);
        });
      }
    
      private getSubCollectionRepository<U>(subCollection: ISubCollection<U>, tran?: Transaction): ITransactionRepository<U> {
        if (tran) {
          return new TransactionRepository(subCollection, tran);
        }
        return subCollection;
      }
    }
  3. Unit Tests:

    • Add unit tests to validate that subcollections use TransactionRepositories within transactions.
    test('should use TransactionRepository for subcollections inside transactions', async () => {
      const bandRepository = getRepository(Band);
    
      await bandRepository.runTransaction(async tran => {
        const band = await tran.findById('band1');
        const albumsRepo = band.albums;
        expect(albumsRepo).toBeInstanceOf(TransactionRepository);
    
        const album = new Album();
        album.id = 'album1';
        album.name = 'Test Album';
    
        await albumsRepo.create(album);
        const savedAlbum = await albumsRepo.findById('album1');
        expect(savedAlbum.name).toBe('Test Album');
      });
    });

Example Implementation

class Album {
  id: string;
  name: string;
}

@Collection()
class Band {
  id: string;
  name: string;
  formationYear: number;
  genres: Array<string>;

  @SubCollection(Album, 'albums')
  albums: ISubCollection<Album>;
}

const bandRepository = getRepository(Band);

await bandRepository.runTransaction(async tran => {
  const band = await tran.findById('band1');
  const albumsRepo = band.albums; // Should be a TransactionRepository

  const album = new Album();
  album.id = 'album1';
  album.name = 'Test Album';

  await albumsRepo.create(album);
  const savedAlbum = await albumsRepo.findById('album1');
  console.log(savedAlbum.name); // Output: 'Test Album'
});

Original Issue

Support for Collection Group Queries

Description

Firestore supports collection group queries, allowing you to search across multiple collections or subcollections with the same name. This feature is useful for querying data without needing to know the parent record. The proposed syntax for this feature in Fireorm is to use a collectionGroup function that derives the collection name from the entity.

Steps to Reproduce

  1. Attempt to query across subcollections with the same name using the current Fireorm implementation.
  2. Note the lack of support for collection group queries.

Expected Behavior

Ability to perform collection group queries using a syntax like collectionGroup(Entity).where(...).

Actual Behavior

Currently, Fireorm does not support collection group queries, limiting the ability to search across multiple collections or subcollections with the same name.

Acceptance Criteria

  • Implement a collectionGroup function in Fireorm that supports collection group queries.
  • Derive the collection group name from the entity name.
  • Ensure type safety using Fireorm's QueryBuilder.
  • Provide a light wrapper around collectionGroup and QueryBuilder to facilitate collection group queries.

Additional Context

  • November 29, 2019: Initial issue raised with a proposed syntax for collection group queries.
  • December 2-4, 2019: Discussion on how to derive the collection group name from the entity and the need for an intermediate utility function.
  • May 30, 2020: Inquiry about progress and use cases for querying specific subcollections across collections.
  • June 1, 2020: Discussion on modeling the collectionGroup method and using Fireorm's QueryBuilder for type safety.
  • August 4, 2020: Mentioned in another issue related to collection group queries.

Proposed API Changes

  1. collectionGroup Function:

    • Introduce a collectionGroup function to support collection group queries.
    • Derive the collection group name from the entity name, similar to how collections are named when saved in the database.
    // Example usage
    const albumRepository = collectionGroup(Album);
    const album70s = albumRepository.whereLessThan('releaseDate', new Date('1980-01-01')).find();
  2. QueryBuilder Integration:

  • Use Fireorm's QueryBuilder to ensure type safety for the collection group queries.
  • Implement a light wrapper around collectionGroup and QueryBuilder to facilitate seamless integration and querying.
  1. Utility Function:
  • Consider implementing an intermediate utility function to extract the intended collection group name.
  • Ensure the utility function accurately derives the collection name from the entity, accommodating scenarios where an entity might belong to multiple collection groups.

Original Issue

(reference to original) FireORM official roadmap

Collaborations are welcome! If you want to start working on a feature that is listed below, please create another issue to start the discussion. This issue is only meant to be used as a placeholder.

v1 roadmap

  • Improvements in api documentation wovalle#78
  • Support for methods (and getters/setters) in models
  • Integration tests with real firestore db wovalle#31
  • Transactions support
  • OrderBy wovalle#30
  • Limit wovalle#18
  • Stricter typescript rules
  • Better error handling (and custom errors)
  • Lazy subcollections (initialize subcollections without having to query the parent collection first) wovalle#51
  • Collection Group Queries

Next major

  • Pagination
  • Firestore events subscription/dispatching support
  • Relationships (1:1, 1:n, n:n, eager, lazy) wovalle#32
  • Firebase js client support (probably)
  • Increase the compatibility with Raw Firestore Documents

Wishlist

  • Javascript support
  • Browser support
  • Firebase Realtime database support
  • Investigate if models can be enforced with db rules (cli project)
  • (cli) Migrations
  • Change tracking (instead of updating the entire document, just update the keys that changed)
  • Custom decorators like @createdOnField, @updatedOnField
  • Custom hooks? onCreate, onUpdate, onDelete, onRead
  • (cli) Indexes management: throw custom errors when trying to do range filters for non-indexes for non indexed fields, creation and deletion of indexes.

Enhance Firestore Complex Types Handling

Issue Title

Enhance Firestore Complex Types Handling

Description

The current method for handling Firestore complex types, specifically using class-transformer's @Type decorator for GeoPoint fields, is a temporary workaround and needs a more robust solution. The main challenge is the lack of runtime type information in TypeScript, requiring a decorator to cast Firestore's complex types into custom classes effectively.

Steps to Reproduce

  1. Use the existing @Type decorator to cast Firestore GeoPoint fields.
  2. Attempt to apply similar methods to other complex Firestore data types.

Expected Behavior

Develop a new decorator that can handle type-casting for all Firestore complex types, allowing custom constructors and enforcing serialization and deserialization methods.

Actual Behavior

The current solution with @Type is limited, particularly for parsing GeoPoint, which requires public latitude and longitude fields.

Acceptance Criteria

  • Explore each Firestore Data Type and create unit tests for complex types.
  • Eliminate the use of class-transformer's @Type decorator.
  • Develop a new decorator (e.g., @Transform) to store field type-casting information and constraints.
  • Ensure the new decorator allows calling custom constructors, enabling class owners to construct objects flexibly.
  • Enforce serialization and deserialization methods for the new decorator to handle custom classes like GeoPoint and other complex types.

Additional Context

This issue has been an ongoing challenge, with various suggestions and discussions about the best approach to handle nested and complex data types in Firestore documents. The goal is to have a flexible, robust solution that can be easily validated and serialized.

Support for Retrieving Model from DocumentSnapshot in Firestore DB Triggers

Description

There is a need to retrieve a model from the DocumentSnapshot provided by Firestore DB triggers. The current solution involves using plainToClass from class-transformer, but a more integrated approach within Fireorm would be beneficial.

Steps to Reproduce

  1. Set up a Firestore DB trigger.
  2. Attempt to retrieve a model directly from the DocumentSnapshot provided by the trigger.

Expected Behavior

A method within Fireorm that allows easy conversion of DocumentSnapshot to a model, enabling seamless use of Fireorm in Firestore DB triggers.

Actual Behavior

Currently, users need to manually convert the snapshot data to their model using plainToClass.

Acceptance Criteria

  • Develop a method (e.g., extractTFromDocSnap(docSnap)) within Fireorm to handle the conversion from DocumentSnapshot to a model.
  • Ensure the method works reliably with all fields declared in the model.
  • Consider future integration of Fireorm as a middleware in triggers to automatically translate payloads.

Additional Context

  • October 18, 2019: Initial inquiry about using Fireorm with Firestore DB triggers.
  • October 19, 2019: Suggested use of plainToClass as a temporary solution.
  • October 21, 2019: Discussion about exposing an internal function for converting document snapshots to models and the potential for future middleware integration.
  • July 3, 2020: Comment about the blocking nature of this issue and offer to help with a solution.
  • July 7, 2020: Mentioned in another issue related to using Fireorm with Firebase Client SDK.

Original Issue

Implement Listening on Snapshot Events

Description

Fireorm lacks support for listening to snapshot events. A proof-of-concept was created to demonstrate the feasibility of this feature, which allows real-time updates by listening to Firestore snapshot events. This feature needs to be refined and integrated into Fireorm with proper abstractions and unit tests.

Steps to Reproduce

  1. Attempt to implement real-time updates by listening to Firestore snapshot events in Fireorm.
  2. Notice the lack of built-in support for this functionality.

Expected Behavior

Ability to listen to Firestore snapshot events and handle real-time updates within Fireorm, using a consistent API that fits with the rest of the project.

Actual Behavior

Currently, Fireorm does not support listening to snapshot events, requiring manual implementation.

Acceptance Criteria

  • Implement listening on snapshot events with proper type abstractions.
  • Replace any usage of any with proper TypeScript types.
  • Create unit tests to ensure the functionality works as expected.
  • Return a custom object with parsed entities and relevant information from the snapshot, instead of the raw QuerySnapshot.

Additional Context

  • November 22, 2020: Initial proof-of-concept for listening to snapshot events.
  • November 23, 2020: Suggestions for improvements, including adding unit tests and proper TypeScript types.
  • November 25, 2020: Discussion about abstracting QuerySnapshot and providing a custom object to users.
  • November 30, 2020: Updates to use generics for QuerySnapshot.
  • December 7, 2020: Further updates and preparation for a pull request to the main repository.

Proposed API Changes

  1. Implement Snapshot Listener:

    • Add a method to listen to Firestore snapshot events and handle real-time updates.
    // Example implementation of listening to snapshot events
    async listenToSnapshots(callback: (data: CustomSnapshot<T>) => void): Promise<() => void> {
      const unsubscribe = this.firestoreCollection.onSnapshot(snapshot => {
        const data = this.extractDataFromSnapshot(snapshot);
        callback(data);
      });
      return unsubscribe;
    }
  2. Custom Snapshot Object:

    • Return a custom object with parsed entities and relevant information from the snapshot, instead of the raw QuerySnapshot.
    // Define a custom snapshot type
    type CustomSnapshot<T> = {
      docs: T[];
      // Add other relevant information if needed
    };
    
    // Example of extracting data from a snapshot
    private extractDataFromSnapshot(snapshot: QuerySnapshot): CustomSnapshot<T> {
      const docs = snapshot.docs.map(doc => this.extractTFromDocSnap(doc));
      return { docs };
    }
  3. Unit Tests:

    • Create unit tests to validate the functionality of the snapshot listener.
    // Example unit test
    test('should listen to snapshot events and handle updates', async () => {
      const callback = jest.fn();
      const unsubscribe = await repo.listenToSnapshots(callback);
      
      // Simulate a snapshot event
      const snapshot = { /* mock data */ };
      callback(snapshot);
      
      expect(callback).toHaveBeenCalledWith(snapshot);
      
      unsubscribe();
    });

Example Implementation

@Collection()
class User {
  id: string;
  display_name: string;
  username: string;
}

// Repository instance
const repo = getRepository(User);

// Listen to snapshot events
const unsubscribe = await repo.listenToSnapshots(data => {
  console.log('Received snapshot:', data);
});

// Later, unsubscribe from snapshot events
unsubscribe();

Original Issue

Support for Referenced Properties

Description

Fireorm currently does not support creating entities with referenced properties directly. Users need a way to reference other collections when creating new documents, such as referencing a Produtor from within a ProdutorDadosAdicionais entity.

Steps to Reproduce

  1. Create a Produtor class and a ProdutorDadosAdicionais class, with ProdutorDadosAdicionais having a property referencing Produtor.
  2. Attempt to create a new ProdutorDadosAdicionais document with a reference to an existing Produtor document.

Expected Behavior

Ability to create documents with referenced properties that correctly store document references in Firestore.

Actual Behavior

Currently, there is no straightforward way to set referenced properties, leading to workarounds or potential bugs with deserialization/serialization of Firestore entities.

Acceptance Criteria

  • Implement support for creating documents with referenced properties, allowing users to set references directly.
  • Ensure compatibility with TypeScript for proper type checking and autocomplete.
  • Avoid forcing the use of subcollections where document references are more appropriate.
  • Develop a method to validate reference paths.
  • Consider adding a getRef/getPath function to entities for easy access to document paths.

Additional Context

  • November 12, 2019: Initial inquiry about setting referenced properties.
  • November 14, 2019: Discussion on potential solutions and workarounds, such as using produtorId and produtorNome properties.
  • February 18, 2021: Request to re-open the issue to further explore a cleaner API for document references.
  • February 22, 2021: Discussion about a proposed API using Reference<T> type and @DocumentReference() decorator.
  • February 28, 2021: Initial work on supporting references, with ongoing discussion about the API design.

Original

Support Custom Repositories in Transactions

Description

Currently, the transaction object in Fireorm only exposes a getRepository method, which returns a TransactionRepository. This limits the ability to use custom repositories within transactions, as there is no getCustomRepository method available.

Steps to Reproduce

  1. Attempt to use a custom repository within a transaction in Fireorm.
  2. Notice that the transaction object does not provide a getCustomRepository method.

Expected Behavior

Ability to use custom repositories within transactions by providing a getCustomRepository method on the transaction object.

Actual Behavior

The transaction object only exposes a getRepository method, which returns a TransactionRepository.

Acceptance Criteria

  • Implement a getCustomRepository method on the transaction object.
  • Ensure the custom repositories within transactions receive the necessary special treatment.
  • Add unit tests to verify the functionality of custom repositories in transactions.

Additional Context

  • December 31, 2020: Initial issue raised about the lack of support for custom repositories in transactions.
  • January 11, 2021: Acknowledgment of the issue and discussion about the special treatment required for transaction repositories.
  • March 7, 2021: Expression of interest in supporting this feature, with an invitation for contributions.

Proposed API Changes

  1. Implement getCustomRepository Method:

    • Add a getCustomRepository method to the transaction object to support custom repositories in transactions.
    class Transaction {
      // Existing methods...
    
      getCustomRepository<T>(entity: EntityConstructor<T>): CustomRepository<T> {
        const repository = getCustomRepository(entity);
        // Apply necessary special treatment for transaction
        return new CustomTransactionRepository(repository, this);
      }
    }
  2. Special Treatment for Transaction Repositories:

    • Ensure that custom repositories within transactions receive the necessary special treatment.
    class CustomTransactionRepository<T> extends CustomRepository<T> {
      constructor(repository: CustomRepository<T>, private transaction: Transaction) {
        super(repository);
        // Apply transaction-specific logic
      }
    
      // Override necessary methods to handle transactions
    }
  3. Unit Tests:

    • Create unit tests to validate the functionality of custom repositories within transactions.
    test('should support custom repositories within transactions', async () => {
      const customRepo = transaction.getCustomRepository(CustomEntity);
      const customEntity = new CustomEntity();
      customEntity.name = 'Test Entity';
    
      await transaction.run(async () => {
        await customRepo.create(customEntity);
      });
    
      const fetchedEntity = await customRepo.findById(customEntity.id);
      expect(fetchedEntity.name).toBe('Test Entity');
    });

Example Implementation

@Collection()
class CustomEntity {
  id: string;
  name: string;
}

// Custom repository
class CustomRepository extends BaseRepository<CustomEntity> {
  // Custom methods...
}

// Transaction using custom repository
const customRepo = getCustomRepository(CustomEntity);

await transaction.run(async () => {
  const customRepoInTx = transaction.getCustomRepository(CustomEntity);
  const customEntity = new CustomEntity();
  customEntity.name = 'Test Entity';

  await customRepoInTx.create(customEntity);
});

// Verify the entity was created
const fetchedEntity = await customRepo.findById(customEntity.id);
console.log(fetchedEntity.name); // Output: 'Test Entity'

Original Issue

Support Custom Validation Libraries

Description

Fireorm is tightly coupled with class-validator for model validation. To provide flexibility, we should allow the use of arbitrary validation libraries. This would involve decoupling Fireorm from class-validator and introducing a validation interface that can accommodate various validation libraries.

Steps to Reproduce

  1. Attempt to use a validation library other than class-validator with Fireorm.
  2. Notice the tight coupling with class-validator and the difficulty in substituting another library.

Expected Behavior

Ability to use any validation library with Fireorm by providing a custom validator through the initialization options.

Actual Behavior

Fireorm is tightly coupled with class-validator, making it challenging to use other validation libraries.

Acceptance Criteria

  • Introduce a validation interface that supports various validation libraries.
  • Modify the initialization options to accept a custom validator.
  • Ensure backward compatibility with the current class-validator usage.
  • Add unit tests to validate the functionality with different validation libraries.

Additional Context

  • March 16, 2022: Initial proposal to support arbitrary validation libraries.
  • March 16, 2022: Agreement on the idea and suggestion to use a type parameter for the validation errors.
  • March 17, 2022: Discussion about the tight coupling with class-validator and the complexity of decoupling.

Proposed API Changes

  1. Introduce Validation Interface:

    • Define a validation interface to support various validation libraries.
    interface IEntityValidator {
      (entity: IEntity, validateOptions?: Record<string, unknown>): Array<unknown>;
    }
  2. Update Initialization Options:

    • Modify the initialization options to accept a custom validator.
    interface IFireormInitOptions  {
      validateModels?: boolean;
      validate?: IEntityValidator;
    }
    
    function initialize(firestore: Firestore, options: IFireormInitOptions = {}) {
      // Existing initialization logic...
      if (options.validateModels && options.validate) {
        this.validator = options.validate;
      } else {
        // Default to class-validator
        const { validate } = require('class-validator');
        this.validator = validate;
      }
    }
  3. Decouple Fireorm from class-validator:

    • Refactor the codebase to use the validation interface instead of directly relying on class-validator.
    class Repository {
      async create(entity: IEntity) {
        if (this.config.validateModels) {
          const errors = this.config.validator(entity);
          if (errors.length) {
            throw errors;
          }
        }
        // Existing create logic...
      }
    }
  4. Unit Tests:

    • Add unit tests to validate the functionality with different validation libraries.
    test('should validate entity using custom validator', async () => {
      const customValidator: IEntityValidator = (entity) => {
        if (!entity.name) return ['Name is required'];
        return [];
      };
    
      initialize(firestore, { validateModels: true, validate: customValidator });
    
      const repo = getRepository(MyEntity);
      const entity = new MyEntity();
      entity.name = '';
    
      await expect(repo.create(entity)).rejects.toEqual(['Name is required']);
    });

Example Implementation

import { getFirestore } from 'my-firestore-setup';
import { initialize } from 'fireorm';

class MyValidator implements IEntityValidator {
  validate(entity: IEntity, validateOptions?: Record<string, unknown>): Array<unknown> {
    const errors = [];
    if (!entity.name) errors.push('Name is required');
    return errors;
  }
}

const customValidator = new MyValidator().validate;

initialize(getFirestore(), { validateModels: true, validate: customValidator });

@Collection("myEntities")
class MyEntity {
  id: string;
  name: string;
}

const repo = getRepository(MyEntity);
const entity = new MyEntity();
entity.id = "1";
entity.name = "";

await repo.create(entity); // Should throw 'Name is required'

Original Issue

Option to Omit Sub-Collections from Queries

Description

There are scenarios where certain queries need to omit sub-collections, particularly when the data is being ingested by a third party. Providing a configuration option to disable sub-collection initialization on a per-request basis would be valuable.

Steps to Reproduce

  1. Perform a query using Fireorm that returns documents with sub-collections.
  2. Attempt to omit sub-collections from the query results without additional processing.

Expected Behavior

Ability to configure queries to exclude sub-collections from the results, either on a per-request basis or as a global setting.

Actual Behavior

Currently, sub-collections are always initialized, and developers must manually remove them if not needed.

Acceptance Criteria

  • Implement a configuration option to disable sub-collection initialization on a per-request basis.
  • Ensure the feature is optional and enabled by default.
  • Consider adding a global setting to disable sub-collection initialization for the entire project.

Additional Context

  • May 21, 2020: Initial issue raised about the need to omit sub-collections from certain queries.
  • June 6, 2020: Discussion on the feasibility and design of the proposed feature, including the possibility of a project-wide toggle.

Proposed API Changes

  1. Per-Request Configuration:

    • Add an optional parameter to query methods to disable sub-collection initialization.
    • Example:
      getRepository(Product).findById('1234', { initializeSubcollections: false });
  2. Global Configuration:

    • Introduce a global setting in the Fireorm initialization to disable sub-collection initialization for all queries.
    • Example:
      initialize({ initializeSubcollections: false });
  3. Default Behavior:

    • Ensure that sub-collection initialization is enabled by default to maintain backward compatibility.
    • Allow developers to override this behavior per query or for the entire project as needed.
  4. Implementation Considerations:

    • Ensure the implementation is clean and does not introduce significant overhead or complexity.
    • Provide clear documentation on how to use the new configuration options.

Example Usage

// Disable sub-collections for a specific query
const product = await getRepository(Product).findById('1234', { initializeSubcollections: false });

// Disable sub-collections globally
initialize({ initializeSubcollections: false });

Original Issue

Implement Select Method for Partial Document Retrieval

Description

Firebase Firestore has a select method that allows retrieving specific fields from a document, similar to the SELECT statement in SQL. This feature can be beneficial for improving performance by reducing the amount of data transferred over the network. Currently, Fireorm does not support this feature.

Steps to Reproduce

  1. Attempt to use the select method in Fireorm to retrieve specific fields from a document.
  2. Notice that the feature is not implemented.

Expected Behavior

Ability to use a select method to specify which fields to retrieve from a document, reducing the amount of data transferred and improving performance.

Actual Behavior

Fireorm always returns the entire document, which can lead to unnecessary data transfer and performance issues.

Acceptance Criteria

  • Implement a select method in Fireorm that allows specifying which fields to retrieve from a document.
  • Ensure the API is type-safe and intuitive to use.
  • Provide documentation and examples on how to use the new feature.

Additional Context

  • September 1, 2020: Initial request to implement a select method for partial document retrieval.
  • September 8, 2020: Acknowledgment that the feature is not implemented and request for API design ideas.
  • September 30, 2020: Agreement on the usefulness of the feature and call for ideas.

Proposed API Changes

  1. Introduce Select Method:

    • Add a select method to the repository that allows specifying which fields to retrieve.
    // Example usage
    const partialUser = await getRepository(User).select('display_name', 'username').findById(userId);
  2. Type-Safe API:

    • Ensure that the select method is type-safe, providing autocomplete and type checking for field names.
  3. Implementation Details:

    • Modify the query builder to support the select method.
    • Ensure that the select method integrates seamlessly with existing query methods in Fireorm.
    • Provide thorough testing to validate the new functionality and ensure it does not introduce any regressions.

Example Implementation

@Collection()
class User {
  id: string;
  display_name: string;
  username: string;
  email: string;
}

// Repository instance
const repo = getRepository(User);

// Retrieve only specific fields
const partialUser = await repo.select('display_name', 'username').findById(userId);

Original Issue

Fix getRepository for Collections and Subcollections with the Same Name

Description

When naming a collection and a subcollection with the same name, the getRepository function returns incorrect results. This issue arises due to the way getRepository prioritizes collections and subcollections.

Steps to Reproduce

  1. Create a collection and a subcollection with the same name.
  2. Use getRepository to retrieve the repository.
  3. Observe that the incorrect repository is returned.

Expected Behavior

The getRepository function should correctly distinguish between collections and subcollections with the same name, giving priority to collections.

Actual Behavior

The getRepository function returns the wrong repository when collections and subcollections have the same name.

Acceptance Criteria

  • Modify getRepository to rank collections first, giving them priority over subcollections.
  • Ensure the function correctly distinguishes between collections and subcollections with the same name.
  • Add unit tests to verify the correct behavior of getRepository in this scenario.

Additional Context

  • October 23, 2020: Initial issue raised about the incorrect behavior of getRepository.
  • November 8, 2020: Acknowledgment of the issue and suggestion to prioritize collections over subcollections.

Original Issue

Handle Undefined Values by Setting Them as Null

Description

To improve query capabilities and data consistency in Fireorm, undefined values in a collection should be set to null. This change would allow querying those values using Where clauses. Additionally, the IFirestoreVal type should be extended to allow null values.

Steps to Reproduce

  1. Create a Fireorm entity with optional fields.
  2. Attempt to query documents where these fields are undefined.

Expected Behavior

Undefined fields should be stored as null in Firestore, allowing queries to include conditions on these null values.

Actual Behavior

Undefined fields are not stored, making it impossible to query for documents based on undefined values.

Acceptance Criteria

  • Modify the serializeEntity function to set undefined values as null before writing data to Firestore.
  • Extend the IFirestoreVal type to include null.
  • Ensure that querying for null values using Where clauses works as expected.

Additional Context

  • June 25, 2020: Initial issue raised about setting undefined values to null.
  • July 7, 2020: Agreement on the idea and labeling the issue as a good first issue.
  • October 18, 2020: Discussion about running integration tests and setting up environment variables for contributing.

Proposed API Changes

  1. Modify serializeEntity Function:

    • Update the serializeEntity function to convert undefined values to null before saving to Firestore.
    function serializeEntity(entity: any): any {
      const serializedEntity = { ...entity };
      Object.keys(serializedEntity).forEach(key => {
        if (serializedEntity[key] === undefined) {
          serializedEntity[key] = null;
        }
      });
      return serializedEntity;
    }
  2. Extend IFirestoreVal Type:

    • Update the IFirestoreVal type def to include null
    export type IFirestoreVal = string | number | boolean | null | ...; // other existing types
  3. Querying Null Values:

    • Ensure that the Where query functionality supports conditions on null values
    // Example query
    getRepository(MyEntity).where('myField', '==', null).find();    

Example Usage

// Define an entity with optional fields
@Collection()
class MyEntity {
  id: string;
  optionalField?: string;
}

// Save an entity with undefined optionalField, which will be stored as null
const entity = new MyEntity();
entity.id = '123';
await getRepository(MyEntity).create(entity);

// Query for entities where optionalField is null
const results = await getRepository(MyEntity).where('optionalField', '==', null).find();

Original Issue

Support Eager Loading for Subcollections

Description

When querying a document that contains a subcollection, the subcollection is not automatically queried and must be queried manually. This enhancement proposes adding support for eager loading of subcollections to simplify querying nested data structures.

Steps to Reproduce

  1. Query a document that contains a subcollection.
  2. Observe that the subcollection is not automatically loaded and must be queried manually.

Expected Behavior

Subcollections should be automatically queried and loaded when querying the parent document, based on specified eager loading properties.

Actual Behavior

Subcollections are not automatically queried and must be manually loaded after querying the parent document.

Acceptance Criteria

  • Implement a mechanism to support eager loading of subcollections.
  • Ensure that the eager loading properties can be specified when querying the parent document.
  • Add unit tests to validate the functionality of eager loading subcollections.

Additional Context

  • April 15, 2022: Initial issue raised about querying subcollections.
  • June 17, 2022: Discussion about the limitations and openness to suggestions.
  • December 13, 2022: Suggestion to implement eager loading properties when querying data.

Proposed API Changes

  1. Add Eager Loading Support:

    • Introduce an eagerLoad method to specify subcollections that should be eagerly loaded when querying the parent document.
    // Example of adding eager loading support to the query builder
    class QueryBuilder<T> {
      private eagerLoadProps: Array<IWherePropParam<T>> = [];
    
      eagerLoad(...props: Array<IWherePropParam<T>>): this {
        for (const prop of props) {
          if (isSubCollection(prop)) {
            this.eagerLoadProps.push(prop);
          }
        }
        return this;
      }
    
      // Other existing methods...
    }
  2. Modify Repository Execution:

    • Update the repository execution method to handle eager loading of subcollections.
    // Example of modifying the repository execution method
    class BaseFirestoreRepository<T> {
      async execute(
        queries: Array<IFireOrmQueryLine>,
        limitVal?: number,
        orderByObj?: IOrderByParams,
        single?: boolean,
        customQuery?: ICustomQuery<T>,
        eagerLoadedProps?: Array<IWherePropParam<T>>
      ): Promise<T[]> {
        let query = queries.reduce<Query>((acc, cur) => {
          const op = cur.operator as WhereFilterOp;
          return acc.where(cur.prop, op, cur.val);
        }, this.firestoreColRef);
        
        const executedQuery = query.get().then(this.extractTFromColSnap);
    
        if (eagerLoadedProps && eagerLoadedProps.length > 0) {
          const results = await executedQuery;
          await Promise.all(
            results.map(async result => {
              for (const prop of eagerLoadedProps) {
                const subCollection = result[prop as keyof T] as ISubCollection<any>;
                result[prop as keyof T] = await subCollection.get();
              }
            })
          );
          return results;
        }
    
        return executedQuery;
      }
    
      // Other existing methods...
    }
  3. Unit Tests:

    • Add unit tests to validate the eager loading functionality for subcollections.
    // Example unit test for eager loading subcollections
    test('should eagerly load subcollections', async () => {
      const bandRepository = getRepository(Band);
      const band = await bandRepository
        .whereGreaterThan(band => band.formationYear, 1985)
        .whereArrayContains(band => band.genres, 'progressive-rock')
        .eagerLoad(band => band.albums)
        .find();
    
      expect(band.albums).toBeDefined();
      expect(band.albums.length).toBeGreaterThan(0);
    });

Example Implementation

class Album {
  id: string;
  name: string;
  year: number;
}

@Collection()
class Band {
  id: string;
  name: string;
  formationYear: number;
  genres: Array<string>;

  @SubCollection(Album, 'albums')
  albums?: ISubCollection<Album>;
}

const bandRepository = getRepository(Band);

const band = await bandRepository
  .whereGreaterThan(band => band.formationYear, 1985)
  .whereArrayContains(band => band.genres, 'progressive-rock')
  .eagerLoad(band => band.albums)
  .find();

console.log(band.albums.map(album => album.name)); // Correctly prints the albums' names

Original Issue

Exclude Reserved Fields from Firestore Documents

Description

Creating or updating documents in Fireorm adds the id field to the actual document. Certain reserved fields in Firestore, such as exists, id, and hasPendingWrites, should not be stored within documents. The presence of these fields can trigger warnings from packages like @nandorojo/swr-firestore.

Steps to Reproduce

  1. Create or update a document using Fireorm.
  2. Check the stored document and observe the presence of the id field.
  3. Use a package like @nandorojo/swr-firestore and observe warnings about reserved fields.

Expected Behavior

Firestore documents should not store reserved fields such as id, exists, and hasPendingWrites.

Actual Behavior

Firestore documents store the id field, leading to warnings and potential inconsistencies.

Acceptance Criteria

  • Provide an option to prevent storing the id field within documents.
  • Ensure reserved fields (exists, id, hasPendingWrites) are not stored in Firestore documents.
  • Add a warning mechanism if reserved fields are being used within documents.

Additional Context

  • November 12, 2020: Initial issue raised about storing reserved fields.
  • November 12, 2020: Discussion about the potential need to exclude reserved fields.
  • April 20, 2021: Comment about the need for a solution to stay consistent with documents not created with Fireorm.
  • May 11, 2021: Acknowledgment of the design perspective and openness to PRs that address the issue.

Proposed API Changes

  1. Update serializeEntity Function:

    • Modify the serializeEntity function to exclude reserved fields from being stored in Firestore documents.
    function serializeEntity(entity: any): any {
      const serializedEntity = { ...entity };
      ['id', 'exists', 'hasPendingWrites'].forEach(field => {
        delete serializedEntity[field];
      });
      return serializedEntity;
    }
  2. Provide Configuration Option:

    • Add a configuration option to control whether the id field should be excluded from documents.
    // Example configuration
    initialize({ excludeReservedFields: true });
  3. Add Warning Mechanism:

    • Implement a warning mechanism that alerts developers if reserved fields are detected within documents.
    function checkForReservedFields(entity: any): void {
      ['exists', 'hasPendingWrites'].forEach(field => {
        if (entity.hasOwnProperty(field)) {
          console.warn(`Warning: Reserved field "${field}" detected in document.`);
        }
      });
    }

Example Implementation

@Collection()
class User {
  id: string;
  display_name: string;
  username: string;
}

// Initialize Fireorm with configuration to exclude reserved fields
initialize({ excludeReservedFields: true });

// Repository instance
const repo = getRepository(User);

// Create or update user document
const user = new User();
user.id = 'user123';
user.display_name = 'John Doe';
user.username = 'johndoe';

repo.create(user); // The `id` field will not be stored in the document

Original Issue

Fix Serialization Error for Objects with Custom Prototypes

Description

Attempting to update a Fireorm object containing a map of custom objects results in a serialization error. Firestore does not support JavaScript objects with custom prototypes, leading to issues when saving such objects.

Steps to Reproduce

  1. Create a Fireorm model containing a map of custom objects.
  2. Attempt to update an instance of this model in Firestore.
  3. Observe the serialization error.

Expected Behavior

The update should serialize the custom objects correctly and save them to Firestore without errors.

Actual Behavior

A serialization error occurs, stating that Firestore does not support JavaScript objects with custom prototypes.

Acceptance Criteria

  • Implement a mechanism to serialize objects with custom prototypes to plain objects before saving to Firestore.
  • Ensure compatibility with Fireorm's existing functionality.
  • Add unit tests to validate the correct serialization of complex data types.

Additional Context

  • May 25, 2021: Initial issue raised about the serialization error.
  • May 30, 2021: Discussion about potential fixes and workarounds, including using a helper function to convert objects to plain objects.
  • May 30, 2021: Suggestion to use a helper function to clean objects of references and prototypes.

Proposed API Changes

  1. Serialize Entities to Plain Objects:

    • Implement a mechanism to serialize entities to plain objects before saving to Firestore.
    import { plainToClass, classToPlain } from 'class-transformer';
    
    async function saveEntity(entity: any) {
      const plainObject = classToPlain(entity);
      await firestore.collection('collectionName').add(plainObject);
    }
  2. Support for Custom Prototypes:

    • Ensure the serialization mechanism supports custom prototypes and complex data types.
    class AuthData {
      id = "";
      email = "";
      displayName = "";
      emailVerified = false;
    }
    
    @Collection("users")
    class User {
      id = "";
      @Type(() => AuthData)
      authData? = new AuthData();
    }
    
    const userRepository = getRepository(User);
    const user = new User();
    user.id = "user123";
    user.authData = new AuthData();
    user.authData.email = "[email protected]";
    
    saveEntity(user);
  3. Helper Function:

    • Provide a helper function to convert objects to plain objects before saving to Firestore.
    function plainObject(obj: any): any {
      return JSON.parse(JSON.stringify(obj));
    }
    
    // Example usage
    const user = new User();
    user.id = "user123";
    user.authData = new AuthData();
    user.authData.email = "[email protected]";
    
    userRepository.update(plainObject(user));
  4. Unit Tests:

    • Add unit tests to validate the serialization of complex data types with custom prototypes.
    test('should serialize complex data types to plain objects', async () => {
      const user = new User();
      user.id = "user123";
      user.authData = new AuthData();
      user.authData.email = "[email protected]";
    
      const plainUser = plainObject(user);
      expect(plainUser.authData.email).toBe("[email protected]");
    });

Example Implementation

class AuthData {
  id = "";
  email = "";
  displayName = "";
  emailVerified = false;
}

@Collection("users")
class User {
  id = "";
  @Type(() => AuthData)
  authData? = new AuthData();
}

const userRepository = getRepository(User);

const user = new User();
user.id = "user123";
user.authData = new AuthData();
user.authData.email = "[email protected]";

userRepository.update(plainObject(user));

Original Issue

Support for Partial Updates

Description

Fireorm currently requires fetching a complete document, updating it, and then passing it to the update() function for any changes. This approach can lead to performance issues and potential data consistency problems when different fields of the same document are being updated concurrently. Implementing partial updates, similar to Firestore's native capabilities, would resolve these issues.

Steps to Reproduce

  1. Create a model with multiple fields.
  2. Attempt to update a single field of a document using Fireorm without fetching the entire document first.
  3. Notice that the current implementation does not support partial updates.

Expected Behavior

Ability to update specific fields of a document without fetching the entire document, using a configuration to specify the update strategy.

Actual Behavior

Currently, the entire document must be fetched, updated, and then saved, leading to potential performance and data consistency issues.

Acceptance Criteria

  • Implement support for partial updates in Fireorm.
  • Add a flag at initialization (updateStrategy: 'replace' | 'merge') with replace as the default.
  • Allow the repository update method to accept a configuration object as a second parameter to locally override the global setting.

Additional Context

  • July 8, 2020: Initial issue raised about the limitation of requiring complete objects for updates.
  • January 21, 2021: Comment on the potential data consistency issues when updating different fields concurrently.
  • February 20, 2021: Proposed implementation strategy to add a flag for update strategy and allow local overrides.

Proposed API Changes

  1. Global Update Strategy Configuration:

    • Introduce a global configuration option during Fireorm initialization to set the default update strategy.
    initialize({ updateStrategy: 'merge' });
  2. Local Override for Update Method:

    • Allow the update method to accept a second parameter for configuration, enabling local override of the update strategy.
    // Default update strategy (e.g., 'replace')
    repo.update({ id: userId, display_name: "new display name" });
    
    // Override update strategy locally to 'merge'
    repo.update({ id: userId, display_name: "new display name" }, { strategy: 'merge' });
  3. Implementation Details:

    • Modify the update method in the repository to handle partial updates based on the configured strategy.
    • Ensure backward compatibility by defaulting to the replace strategy.
    • Provide thorough testing to validate the new functionality and ensure it does not introduce any regressions.

Example Implementation

@Collection()
class UserPublicModel {
  id: string;
  display_name: string;
  username: string;
}

// Initialize Fireorm with global update strategy
initialize({ updateStrategy: 'merge' });

// Repository instance
const repo = getBaseRepository(UserPublicModel);

// Partial update using default strategy
repo.update({ id: userId, display_name: "new display name" });

// Partial update with local override
repo.update({ id: userId, display_name: "new display name" }, { strategy: 'merge' });

Original Issue

Implement Pagination Cursors for Firestore

Description

Firestore supports pagination cursors such as startAt, endAt, and startAfter, which are recommended over using offsets due to performance and cost implications. The current implementation of Fireorm does not fully support these cursors, and there is a need to develop a clean API to handle pagination efficiently. For more information on why offsets are discouraged and how cursors work, refer to the Firestore best practices and Firestore query cursors documentation.

Steps to Reproduce

  1. Attempt to use startAfter or other pagination cursors with the current Fireorm implementation.
  2. Note the lack of support and limitations in extending the existing repository classes.

Expected Behavior

A new or improved API within Fireorm that supports Firestore pagination cursors, allowing for efficient and cost-effective data retrieval without relying on offsets.

Actual Behavior

Current Fireorm implementation does not support Firestore pagination cursors effectively, leading to performance issues and additional costs when using offsets.

Acceptance Criteria

  • Develop functions in the query builder similar to Firestore's standard library functions (startAfter, startAt, etc.).
  • Update the query builder to return an object containing both results and cursor information:
  {
    results: T[], // results of query
    cursor: DocumentRef // document ref of last result
  }
  • Ensure the new functions provide a clean and efficient API for pagination.
  • Consider the impact on the return type of queries and manage any breaking changes effectively.

Additional Context

  • October 8, 2019: Initial issue opened to discuss implementation of pagination cursors.
  • June 9, 2020: Comment by @dmythro about the lack of current support and a suggestion to extend repository classes for a quick fix.
  • December 7, 2020: @garviand suggested adding functions to the query builder or updating the execute function to include cursor information.
  • December 9, 2020: Mention of a proof of concept and further exploration of possible implementations.
  • February 18, 2021: @garviand noted issues with stringifying arrays and suggested limitations in practical use.
  • April 22, 2021, August 25, 2021: Inquiries about updates, with no active development on this feature.

Original Issue

Support Multiple SubCollections of the Same Type

Description

Defining multiple subcollections of the same type within a Fireorm model causes an error. While there are workarounds, such as extending the class, this should ideally be supported without errors. The issue seems to stem from the way MetadataStorage.setCollection() evaluates children before parents and updates segments.

Steps to Reproduce

  1. Create a model with multiple subcollections of the same type.
  2. Attempt to use the model in Fireorm.
  3. Observe the error caused by having multiple subcollections of the same type.

Expected Behavior

Fireorm should support multiple subcollections of the same type within a model without causing errors.

Actual Behavior

An error is thrown when defining multiple subcollections of the same type within a model.

Acceptance Criteria

  • Modify MetadataStorage.setCollection() to correctly handle multiple subcollections of the same type.
  • Ensure that parent and child collections are evaluated and updated correctly.
  • Add unit tests to validate the support for multiple subcollections of the same type.

Additional Context

  • April 26, 2021: Initial issue raised about the error caused by multiple subcollections of the same type.
  • April 6, 2022: Additional comments about the need for a solution and suggestions for workarounds.
  • April 7, 2022: Reference to a related issue.

Proposed API Changes

  1. Update MetadataStorage.setCollection:

    • Modify the MetadataStorage.setCollection() method to correctly handle multiple subcollections of the same type.
    class MetadataStorage {
      setCollection(collection: any) {
        // Logic to handle multiple subcollections of the same type
        // Ensure parent and child collections are evaluated and updated correctly
      }
    }
  2. Support Multiple SubCollections:

    • Ensure the model can support multiple subcollections of the same type without errors.
    @Collection()
    class Band {
      id: string;
      name: string;
      @SubCollection(Album, 'albums')
      albums: ISubCollection<Album>;
      @SubCollection(Album, 'singles')
      singles: ISubCollection<Album>;
    }
  3. Unit Tests:

    • Add unit tests to validate the correct handling of multiple subcollections of the same type.
    test('should support multiple subcollections of the same type', () => {
      const bandRepository = getRepository(Band);
      const band = new Band();
      band.id = 'band1';
      band.name = 'Test Band';
    
      const album = new Album();
      album.id = 'album1';
      album.name = 'Test Album';
    
      const single = new Album();
      single.id = 'single1';
      single.name = 'Test Single';
    
      band.albums.create(album);
      band.singles.create(single);
    
      expect(band.albums.findById('album1').name).toBe('Test Album');
      expect(band.singles.findById('single1').name).toBe('Test Single');
    });

Example Implementation

class Album {
  id: string;
  name: string;
}

@Collection()
class Band {
  id: string;
  name: string;
  @SubCollection(Album, 'albums')
  albums: ISubCollection<Album>;
  @SubCollection(Album, 'singles')
  singles: ISubCollection<Album>;
}

const bandRepository = getRepository(Band);

const band = new Band();
band.id = 'band1';
band.name = 'Test Band';

const album = new Album();
album.id = 'album1';
album.name = 'Test Album';

const single = new Album();
single.id = 'single1';
single.name = 'Test Single';

bandRepository.create(band);
band.albums.create(album);
band.singles.create(single);

const fetchedAlbum = band.albums.findById('album1');
const fetchedSingle = band.singles.findById('single1');

console.log(fetchedAlbum.name); // Output: 'Test Album'
console.log(fetchedSingle.name); // Output: 'Test Single'

Original Issue

Support Complex Data Types with Custom Prototypes

Description

Firestore does not support JavaScript objects with custom prototypes, such as those created via the new operator. This limitation causes issues when trying to save instances of classes with custom prototypes to Firestore. A workaround is needed to serialize these objects correctly before storing them in Firestore.

Steps to Reproduce

  1. Create a class with custom prototypes.
  2. Attempt to save an instance of this class to Firestore using Fireorm.
  3. Observe the error stating that Firestore does not support objects with custom prototypes.

Expected Behavior

Ability to save instances of classes with custom prototypes to Fireorm, with proper serialization to comply with Firestore's requirements.

Actual Behavior

Firestore throws an error when attempting to save objects with custom prototypes.

Acceptance Criteria

  • Implement a serialization mechanism to convert objects with custom prototypes to plain objects before saving to Firestore.
  • Ensure compatibility with Fireorm's existing functionality.
  • Add unit tests to validate the correct serialization of complex data types.

Additional Context

  • March 2, 2021: Initial issue raised about problems with complex data types.
  • March 8, 2021: Acknowledgment of the issue and request for a minimal reproduction repo.
  • March 11, 2021: Discussion about Firestore's limitation and suggestion to serialize objects to plain objects.
  • April 27, 2021: Mention of a pull request potentially solving the issue.
  • May 11, 2021: Clarification on the need for a generic mapping between Firestore "primitives" and fields in custom models.

Proposed API Changes

  1. Serialize Entities to Plain Objects:

    • Implement a mechanism to serialize entities to plain objects before saving to Firestore.
    import { plainToClass, classToPlain } from 'class-transformer';
    
    async function saveEntity(entity: any) {
      const plainObject = classToPlain(entity);
      await firestore.collection('collectionName').add(plainObject);
    }
  2. Support for Custom Prototypes:

    • Ensure the serialization mechanism supports custom prototypes and complex data types.
    class GeoP {
      latitude: number;
      longitude: number;
    }
    
    @Collection()
    class SomeModel {
      @Type(() => GeoP)
      geop: GeoP;
    }
    
    const someGeoPInstance = new GeoP();
    someGeoPInstance.latitude = 19.3753433;
    someGeoPInstance.longitude = -99.0438667;
    
    const someModelInstance = new SomeModel();
    someModelInstance.geop = someGeoPInstance;
    
    saveEntity(someModelInstance);
  3. Unit Tests:

    • Add unit tests to validate the serialization of complex data types with custom prototypes.
    test('should serialize complex data types to plain objects', async () => {
      const someGeoPInstance = new GeoP();
      someGeoPInstance.latitude = 19.3753433;
      someGeoPInstance.longitude = -99.0438667;
    
      const someModelInstance = new SomeModel();
      someModelInstance.geop = someGeoPInstance;
    
      const plainObject = classToPlain(someModelInstance);
      expect(plainObject).toEqual({
        geop: {
          latitude: 19.3753433,
          longitude: -99.0438667,
        },
      });
    });

Example Implementation

class GeoP {
  latitude: number;
  longitude: number;
}

@Collection()
class SomeModel {
  @Type(() => GeoP)
  geop: GeoP;
}

const someGeoPInstance = new GeoP();
someGeoPInstance.latitude = 19.3753433;
someGeoPInstance.longitude = -99.0438667;

const someModelInstance = new SomeModel();
someModelInstance.geop = someGeoPInstance;

const plainObject = classToPlain(someModelInstance);
firestore.collection('SomeModel').add(plainObject);

Original Issue

Include Firestore Timestamps in Entities

Description

When using the Firestore admin SDK, we can retrieve document timestamps (createTime, updateTime, readTime). These timestamps are useful for backend applications that utilize Firestore. This proposal suggests including these timestamps in Fireorm entities, allowing users to automatically access them without manual creation.

Steps to Reproduce

  1. Create an entity and save it to Firestore using Fireorm.
  2. Retrieve the entity and attempt to access its timestamps (createTime, updateTime, readTime).

Expected Behavior

Entities should automatically include createTime, updateTime, and readTime properties, populated with the corresponding Firestore document timestamps.

Actual Behavior

Currently, entities do not include these timestamps automatically and require manual creation and management.

Acceptance Criteria

  • Add support for Firestore timestamps (createTime, updateTime, readTime) in entities.
  • Ensure that these properties can be overridden by the user if necessary.
  • Provide TypeScript support for the new properties.
  • Add unit tests to validate the inclusion and correctness of the timestamps.

Additional Context

Proposed API Changes

  1. Add Timestamp Properties to Entities:

    • Modify the entity base class to include createTime, updateTime, and readTime properties.
    class BaseEntity {
      id: string;
      createTime?: Date;
      updateTime?: Date;
      readTime?: Date;
    }
  2. Update Repository Methods:

    • Update repository methods to populate these properties from Firestore document metadata.
    class BaseFirestoreRepository<T extends BaseEntity> {
      private extractTFromDocSnap(docSnap: DocumentSnapshot): T {
        const entity = docSnap.data() as T;
        entity.createTime = docSnap.createTime?.toDate();
        entity.updateTime = docSnap.updateTime?.toDate();
        entity.readTime = docSnap.readTime?.toDate();
        return entity;
      }
    
      async findById(id: string): Promise<T | null> {
        const docSnap = await this.firestoreColRef.doc(id).get();
        return docSnap.exists ? this.extractTFromDocSnap(docSnap) : null;
      }
    }
  3. Provide TypeScript Support:

    • Add a Timestamped wrapper to add typing for the new properties.
    export type Timestamped<T> = T & {
      createTime: Date;
      updateTime: Date;
      readTime: Date;
    };
  4. Unit Tests:

    • Add unit tests to validate the inclusion and correctness of the timestamps.
    test('should include Firestore timestamps in entities', async () => {
      const todoRepository = getRepository(Todo);
      const todo = new Todo();
      todo.text = "Check fireorm's Github Repository";
      todo.done = false;
    
      const createdTodo = await todoRepository.create(todo);
      const retrievedTodo = await todoRepository.findById(createdTodo.id);
    
      expect(retrievedTodo).not.toBeNull();
      expect(retrievedTodo.createTime).toBeInstanceOf(Date);
      expect(retrievedTodo.updateTime).toBeInstanceOf(Date);
      expect(retrievedTodo.readTime).toBeInstanceOf(Date);
    });

Example Implementation

import { Collection, getRepository } from 'fireorm';

@Collection()
class Todo extends BaseEntity {
  text: string;
  done: boolean;
}

const todoRepository = getRepository(Todo);

const todo = new Todo();
todo.text = "Check fireorm's Github Repository";
todo.done = false;

const todoDocument = await todoRepository.create(todo); // Create todo

const mySuperTodoDocument = await todoRepository.findById(todoDocument.id); // Read todo. Also retrieves createTime, updateTime and readTime from the Firestore DocumentRef

console.log(mySuperTodoDocument.createTime); // Output: 2022-12-17 00:00:00
console.log(mySuperTodoDocument.updateTime); // Output: 2022-12-17 00:00:00
console.log(mySuperTodoDocument.readTime); // Output: 2022-12-17 00:00:01

Original Issue

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    πŸ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. πŸ“ŠπŸ“ˆπŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❀️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.