GithubHelp home page GithubHelp logo

mikkopaderes / ember-cloud-firestore-adapter Goto Github PK

View Code? Open in Web Editor NEW
69.0 69.0 17.0 6.69 MB

Unofficial Ember Data Adapter and Serializer for Cloud Firestore

License: MIT License

JavaScript 11.80% HTML 1.55% CSS 0.22% Handlebars 3.50% TypeScript 82.93%
ember-addon firebase firestore

ember-cloud-firestore-adapter's Introduction

Hi there πŸ‘‹

LinkedIn

ember-cloud-firestore-adapter's People

Contributors

charlesfries avatar douweh avatar ember-tomster avatar gabrielgrant avatar mikkopaderes avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

ember-cloud-firestore-adapter's Issues

Unexpected behaviour when loading relationships

Hi @mikkopaderes, I'm having a few issues loading records with a hasMany relationship. I have some customers that can have many bookings. As per the docs, a reference to each root-level booking is stored in each customer's sub-collection.

When I store.findAll('booking', {}) I get all bookings and if I subsequently run customer.bookings.toArray() I can see the relevant records for that one customer. However, if I skip the first step (i.e. I don't load all bookings first) the second query returns an empty array.

I'm I missing something, or assuming a behaviour that's not in your design?

Fastboot + firebase functions

Could you flesh out the architecture of using a service worker connected to a functions app server? I must be missing a puzzle piece since my service worker doesn't try to connect to my running functions port to get the correct custom tokenID. Instead, since it doesn't use the functions app server, the tokenID it has is "incorrect", which means it's not authenticated in fastboot. I'm hoping for a little more guidance in setting this up.

Query fails if doc referenced by referenceTo field does not exist

Lets say I have post model and posts collection and elsewhere in my db I have a sub-collection of posts that are docs with a referenceTo field to the actual posts collection.

If I perform a query on the sub-collection and one of the reference fields references a post that doesn't exist the query rejects (findRecord rejects on falsy snapshop.exists property) eg.

this.store.query('post', { 
   buildReference(db) { 
      return db.collection('some').doc('docWithPostSubCollectionOfReferenceTos').collection('posts'); 
   }
}

rejects if one of the references is broken.

But given that Firestore operates in the mode that a given DocumentReference will return a DocumentSnapshot with an exists property = false if there is no snapshot at the reference, would it be more more appropriate for the adapter to return a record that satisfies the DS.Model isEmpty state?

(In my particular scenario, I have instances where the referenceTo field can legitimately exist without the referenced record - long running cascading delete functions that run on an onDelete trigger. If the user goes elsewhere in the app I would prefer the queries to succeed and handle the isEmpty records in template logic.)

Maybe an adapterOptions to determine how missing references are handled?

can`t createRecord without id

const newPost = this.store.createRecord('post', { title: 'Post A' });

the error is "Cannot read property 'toString' of undefined TypeError: Cannot read property 'toString' of undefined"
in the function

generateIdForRecord(_store, type) {
  const db = this.firebase.firestore();
  const collectionName = (0, _buildCollectionName.default)(type.modelName);
  return db.collection(collectionName).doc().id;
}

var type is just string (Model name) so type.modeName is undefined

Accessing deeply nested collections

Hi @mikkopaderes, thanks for a great adapter!

I have collections clients -> athletes and clients -> teams. everything works great. I can access athletes and teams with calls like client.get('athletes) and client.get('teams').

when i add a many-to-many relationship between athletes and teams everything mostly works as expected but i can no longer use accessors to retrieve child items. eg. if in a model hook i have loaded a team then any attempt to load a team's athletes via team.get('athletes') will return an empty recordset and i can only retrieve a team' s athletes via a store.query.

If I inspect the buildReference for a team's athletes, the path appears as team\:team_id\athletes. I'm guessing this would need to be clients\:client_id\teams\:team_id\athletes but I don't know how this would be doable in a model definition?

Many to many Stored in Subcollection

Thanks for the grate work!
I was wondering if there is a reason that many to many references are stored in a subcollection and not an array? This is costs time and money as a lot more documents need to be read.

Thanks for the feedback!

Issue signing in with firebase "auth.signInWithPhoneNumber"

Hi @mikkopaderes. Hope you are keeping safe.

This is not so much an issue with this addon perse but a request for assistance. You have exposed the firebase API for sign in with email and password but I am struggling to enable "signInWithPhoneNumber". Would you be able to give me some pointers on this? I tried to follow the firebase authentication documentation to no avail.

I have created a repository with a working implementation of your "auth.signInWithEmailAndPassword" here: git clone https://[email protected]/alexmasita/kenya.united.git.

It is a public repo. I have implemented the sign-in method in the "sign-in" component which works. You can also see the commented sections where I tried to implement the "signInWithPhoneNumber". The error I kept getting was on the following code:

//Predicated the firebase handle with this. as you have exposed it as service in your library.
window.recaptchaVerifier = new this.firebase.auth.RecaptchaVerifier('sign-in-button', {
  'size': 'invisible',
  'callback': function(response) {
    // reCAPTCHA solved, allow signInWithPhoneNumber.
    onSignInSubmit();
  }
});

Specifically, I was getting new this.firebase.auth is not a constructor. I tried to change it to new this.firebase.auth(). to include the opening and closing braces but it was throwing a different error that says something like cannot call bind of undefined which seems to be thrown inside the vendor/fastboot-shims/firebase/firebase-app.js

Let me know if you can help.

Multiple filters support

User Story:

As a user, I want to be able to conditionally apply multiple filters when querying Firebase.

Given 3 query parameters that are mutated by the User from the UI;
In model hook, I want to build the query based on params that the user interacted to.

Code example

model(params) {
  return this.store.query('modelName', {
    filter(reference) {
      return reference
        .orderBy('property-1')
        .where('property-1', '>', params['queryParam-1'])   // executed **only** when the user changes queryParam-1 query
        .where('property-1', '<', params['queryParam-2'])   // executed **only** when the user changes queryParam-2 query
        .where('property-2', '==', params['queryParam-3'])  // executed **only** when the user changes queryParam-3 query
    }
  })
}

New Find Record and Query API

Ideas here cool. Like to implement. Much awesome. Snippets below.

const userId = 'user_a';

this.store.query('user', {
  // Not overriding this hook will point to db.collection('users')
  buildReference(db) {
    return db.collection('users').doc(userId).collection('friends');
  },

  filter(reference) {
    return reference.where('age', '>=', 18).limit(10);
  }
});

this.store.findRecord('user', 'user_id', {
  adapterOptions: {
    buildReference(db) {
      return db.collection('admins');
    }
  }
});

Old API will be removed. 😒

Authentication?

Hey @rmmmp I'm so pumped for the future of Firestore, and I'm so grateful that you took the time to do this! Honestly, I want to start using this right away in a new project.
However, how should I handle authentication?

Peer dependency problems

Hi @mikkopaderes and thanks for this project, it's been a blessing!

Our app has been running nicely with the current version of the add-on for months. For whatever reason, we've ended up with [email protected] installed as a dev dependency. That's not been a problem until today, when I tried to test the app against the latest version of Ember, via ember-try. When I did, it refused to play ball because your add-on has [email protected] as a peer dependency. I tried rolling back to that version but that broke the app - auth doesn't appear to be working properly and that's preventing the adaptor resolving requests 😒

Currently, the only way I can get the app working is with @9.0.1 as a dev dependency.

Before I throw myself into another day of debugging, is there a rationale for having the legacy dependency as a peer? I note that @9.0.1 is in your own dev dependencies, so it seems odd at first glance.

Any context / thoughts would be greatly appreciated πŸ‘πŸ»

Model a subcollection

Hi,

thanks for the handy adapter.

I've stumbled upon a problem while trying to model a document with a subcollection. I tried using a hasMany relationship, but it seems like it's always searching for a root collection?

export default DS.Model.extend({
    fragments: hasMany('fragment') // this should be a subcolletion in firestore
});

Is there a way to access a subcollection in a oneToMany relationship ?

belongsTo referencing documents in a subcollection

Are belongsTo associations to documents in subcollections supported or planned? For example:

belongsTo('user') --> docRef: /groups/mygroup/users/me

Currently works as I'd expect if user with id 'me' is already in the store. If the user is not in the store however it is retrieved from '/users/me' despite the docRef stored.

Question: What is the best way to integrate the adapter with the Firebase performance monitoring

We have an existing ember app using the adapter and we added Firebase’s β€˜newish’ performance monitoring service. The drop-in http profiling is very limited and groups all of our Firestore queries into one big bucket.

What would you recommend as the best approach to capturing more fine grained custom traces of Firestore interactions made through your adapter? Any tips or generally direction is greatly appreciated.

isRealtime issues

Currently this doesnt return any values in my app:

{ adapterOptions: { isRealtime: false } }

However this gives me records but also socketio errors:

{ adapterOptions: { isRealtime: true } }

Anyone else had this issue?

Improve Docs

Docs doesn't mention how you setup the serializer.
It was pretty guessable though.

After generating an application serializer with ember generate serializer application
One needs to change the file content to:

import CloudFirestoreSerializer from 'ember-cloud-firestore-adapter/serializers/cloud-firestore';

export default class ApplicationSerializer extends CloudFirestoreSerializer {
}

Making Retrieval Possible in Fastboot (Enhancement)

@mikkopaderes. This Addon is great so far and I would not want to raise this as an issue but an enhancement. It would be great if the Find Record firebase calls could work in fastboot as well so we:

  1. Do not have to put fastboot checks on the calls.
  2. Benefit from server side retrieval therefore have more info on the relevant pages on first render.
  3. As well as gain the SEO benefits of server side rendering for firebase data.

I would assume the adapter would need to be re-architected in such a way as to be able to use node firebase libraries to retrieve the data.

As stated, this is not an issue, so feel free to close it if you think it is not something that could be on your road map soon. I would be willing to look into this as well with a view to submit a pull request. Thank you.

Adapter Options Real Time Tracker breaks in Fastboot

Hi @mikkopaderes. Have been refactoring my app to clean out the fastboot checks. Have hit a snug that appears related to the real time tracker that you enable in the route model as seen here:

  model(_params) {
    return this.store.findRecord('rider', get(this, 'session.data.authenticated.user.uid'), {
      adapterOptions: {
        isRealtime: true //When enabled breaks the fastboot runtime but loads correctly on client
      }
    });
  }

The error I receive is the following:

DEPRECATION: Attempted to call store.peekRecord(), but the store instance has already been destroyed. [deprecation id: ember-data:method-calls-on-destroyed-store]
        at logDeprecationStackTrace (/Users/alexmasita/External/Personal/projects/Ember-Apps/mobile-services-app/dist/assets/@ember/debug/lib/deprecate.js:103:1)
        at HANDLERS.(anonymous function) (/Users/alexmasita/External/Personal/projects/Ember-Apps/mobile-services-app/dist/assets/@ember/debug/lib/handlers.js:23:1)
        at raiseOnDeprecation (/Users/alexmasita/External/Personal/projects/Ember-Apps/mobile-services-app/dist/assets/@ember/debug/lib/deprecate.js:130:1)
        at HANDLERS.(anonymous function) (/Users/alexmasita/External/Personal/projects/Ember-Apps/mobile-services-app/dist/assets/@ember/debug/lib/handlers.js:23:1)
        at invoke (/Users/alexmasita/External/Personal/projects/Ember-Apps/mobile-services-app/dist/assets/@ember/debug/lib/handlers.js:35:1)
        at Object.deprecate (/Users/alexmasita/External/Personal/projects/Ember-Apps/mobile-services-app/dist/assets/@ember/debug/lib/deprecate.js:168:1)
        at assertDestroyedStore (/Users/alexmasita/External/Personal/projects/Ember-Apps/mobile-services-app/dist/assets/addon-tree-output/ember-data/-private.js:12941:1)
        at Class.peekRecord (/Users/alexmasita/External/Personal/projects/Ember-Apps/mobile-services-app/dist/assets/addon-tree-output/ember-data/-private.js:11016:1)
        at Object.docRef.onSnapshot.docSnapshot [as next] (/Users/alexmasita/External/Personal/projects/Ember-Apps/mobile-services-app/dist/assets/addon-tree-output/ember-cloud-firestore-adapter/utils/realtime-tracker.js:39:1)
        at next (/Users/alexmasita/External/Personal/projects/Ember-Apps/mobile-services-app/node_modules/@firebase/firestore/src/api/database.ts:1135:20)
        at Timeout._onTimeout (/Users/alexmasita/External/Personal/projects/Ember-Apps/mobile-services-app/node_modules/@firebase/firestore/src/util/async_observer.ts:51:11)
        at ontimeout (timers.js:436:11)
        at tryOnTimeout (timers.js:300:5)
        at listOnTimeout (timers.js:263:5)
        at Timer.processTimers (timers.js:223:10)
DEPRECATION: Attempted to call store.hasRecordForId(), but the store instance has already been destroyed. [deprecation id: ember-data:method-calls-on-destroyed-store]
        at logDeprecationStackTrace (/Users/alexmasita/External/Personal/projects/Ember-Apps/mobile-services-app/dist/assets/@ember/debug/lib/deprecate.js:103:1)
        at HANDLERS.(anonymous function) (/Users/alexmasita/External/Personal/projects/Ember-Apps/mobile-services-app/dist/assets/@ember/debug/lib/handlers.js:23:1)
        at raiseOnDeprecation (/Users/alexmasita/External/Personal/projects/Ember-Apps/mobile-services-app/dist/assets/@ember/debug/lib/deprecate.js:130:1)
        at HANDLERS.(anonymous function) (/Users/alexmasita/External/Personal/projects/Ember-Apps/mobile-services-app/dist/assets/@ember/debug/lib/handlers.js:23:1)
        at invoke (/Users/alexmasita/External/Personal/projects/Ember-Apps/mobile-services-app/dist/assets/@ember/debug/lib/handlers.js:35:1)
        at Object.deprecate (/Users/alexmasita/External/Personal/projects/Ember-Apps/mobile-services-app/dist/assets/@ember/debug/lib/deprecate.js:168:1)
        at assertDestroyedStore (/Users/alexmasita/External/Personal/projects/Ember-Apps/mobile-services-app/dist/assets/addon-tree-output/ember-data/-private.js:12941:1)
        at Class.hasRecordForId (/Users/alexmasita/External/Personal/projects/Ember-Apps/mobile-services-app/dist/assets/addon-tree-output/ember-data/-private.js:11072:1)
        at Class.peekRecord (/Users/alexmasita/External/Personal/projects/Ember-Apps/mobile-services-app/dist/assets/addon-tree-output/ember-data/-private.js:11024:1)
        at Object.docRef.onSnapshot.docSnapshot [as next] (/Users/alexmasita/External/Personal/projects/Ember-Apps/mobile-services-app/dist/assets/addon-tree-output/ember-cloud-firestore-adapter/utils/realtime-tracker.js:39:1)
        at next (/Users/alexmasita/External/Personal/projects/Ember-Apps/mobile-services-app/node_modules/@firebase/firestore/src/api/database.ts:1135:20)
        at Timeout._onTimeout (/Users/alexmasita/External/Personal/projects/Ember-Apps/mobile-services-app/node_modules/@firebase/firestore/src/util/async_observer.ts:51:11)
        at ontimeout (timers.js:436:11)
        at tryOnTimeout (timers.js:300:5)
        at listOnTimeout (timers.js:263:5)
        at Timer.processTimers (timers.js:223:10)

I discovered the error disappears when I remove the following adapter options setting:

adapterOptions: {
        isRealtime: true
      }

I however need the real time aspects on the client. Might you know a work-around for this?

save many-to-none reference to db

Hi,

First of all, thanks for the great tool!!

I am trying to add a child to a parent doc with a many-to-none relationship between them. But both object exists before I create the relationship. (I use version 0.5.0 of the adapter)

Parent model:

children: hasMany('child', {
    inverse: null,
    buildReference(db, record) {
      return db.collection('parents').doc(record.get('id')).collection('children');
    }
  }),

The child model doesn't have a reference to the parent, because I don't need that.

When a user adds a child to a parent, I am doing:

parent.get('children').pushObject(child);

My question is, how do I persist this in the database?
If I do parent.save() it doesn't create a subcollection reference to the child. Also parent.get('hasDirtyAttributes') returns false.

Maybe I just don't find this in the docs. In that case sorry for the inconvenience, but could you direct me to a solution?

Thanks in advance!

"Broccoli Builder ran into an error with `Concat` plugin"

Hey @rmmmp, I get an error with a fresh install of your plugin:

Build Canceled: Broccoli Builder ran into an error with `Concat` plugin. ðŸ’Β₯ ENOENT: no such file or directory, open '/Users/Benjamin/Sites/my-project/tmp/source_map_concat-input_base_path-XIEBNak7.tmp/node_modules/firebase/firebase.js'

I tried a basic rm -rf node_modules + yarn install but it didn't do it. Do you have any ideas why? To me it looks like it's not linking well with ember-firebase-service.

I didn't have the problem in 0.2.0.

Support buildReference for one-to-many relationships

Spin-off from the discussions in #150

User Story:

As a user, I want to support buildReference on a one-to-many relationship, so that I can choose to save my records under any collections/sub-collections

Acceptance Criteria:

  • Support buildReference on hasMany one-to-many relationship type
  • Support buildReference on belongsTo relationships
    • This is only used for determining what collection/sub-collection this relationship will use when saving the Reference data type property.
    • When fetching the relationship, it will bypass the buildReference and simply fetch whatever the Reference data type resolves to

Namespaced/nested models

I have models that are in a subdirectory of my models folder e.g. models/other/contract. I think they are called "namespaced models" in the docs. I am not referring to subcollections.

When I save these namespaced models, I get the following error:

Invalid collection reference. Collection references must have an odd number of segments, but other/contracts has 2

So it seems the adapter is trying to write to a collection with a slash in the collection identifier. I can get around the error by rewriting the Firestore collection name as other.contracts, but is there a recommended way of doing this?

dummy application showing relationships

Please publish a dummy application or provide working examples of configuring data models for various relationships, including examples of CRUD methods.

For example, I'm having a hard time configuring two models with many-to-many relationships, where references are stored in their respective sub-collections.

v1.0.0 Goal

TODO:

  • Update addon to use Ember v3.22.0 as well as modernize the syntax #122 #123
  • Update dependency of ember-firebase-service to v8.0.0 #125
  • Convert to TypeScript for better maintainability
  • Move docs to simple markdown files so that we could have proper versioning for it. Then maybe at some point pick up a well known static docs system.
  • See feasibility to use Firebase Emulator for testing instead of mock-cloud-firestore
    • Add support for Firebase Emulator
    • Remove mock-cloud-firestore from project
  • Move firestoreSettings to config/environment.js
  • Setup Firestore configurations in an instance-initializer

Query belongsTo

Given a model Comment that belongsTo User (User hasMany Comments).
Since one-to-many relationships only keep the reference in the belongsTo side.

What should be the preferred way to query all comments from a specific User?

Authentication Integration

Great work on this.

Would like to migrate to this from emberfire but has the authentication story been fleshed out? With emberfire we can add authentication to the app using a torii provider built into EmberFire and the library emberfire-utils has a handy addon firebase-ui-auth which makes authentication integration a breeze.

Make it possible to use an existing client side model Id to create new model

In firebase client libraries, it is possible to use for instance a user id as the id on multiple objects containing that user's information.

In this particular case, I find myself wanting to extend firebase auth object with additional attributes and it would be nice to pick the user id from the session object and create a profile document whose id is the same as the current session's user id.

Is there a way to do it currently when creating a new model via get(this, 'store').createRecord('profile',{}) where you can pass the current session user id as the profile model id?

Auth providers creation

Hi, thanks for your work!
I'm trying to do the authentication with Google, taking the same example they have:

var provider = new firebase.auth.GoogleAuthProvider();
firebase.auth().signInWithPopup(provider).then(function(result) {
 // This gives you a Google Access Token.
 var token = result.credential.accessToken;
 // The signed-in user info.
 var user = result.user;
});

I translated the code to use your authentication and the firebase service, and the issue I have is creating the provider:

var provider = new this.firebase.auth.GoogleAuthProvider();

because this.firebase.auth.GoogleAuthProvider is undefined but this.firebase.auth() does exist. If I use firebase from window it works:

var provider = new window.firebase.auth.GoogleAuthProvider();

The firebase service has the this.firebase.auth() but no this.firebase.auth.GoogleAuthProvider

Any ideas why this is happening? thanks!

Is there a way to Enable offline data

As seen on the official docs, Cloud firestore enables you to use the cache when you loose the internet connection so that the app does not break. Is there an easy way to configure this here?

You can check out the article here: firestore enable persistence link

It would be great to be able to easily manually disable and enable network access programmatically as seen on that page as well.

Thank you.

Feature request: Add error name to error thrown when fetchRecord fails

the adapter's findRecord etc call an underlying fetchRecord function which rejects with a js Error object when a record isn't found:

reject(new Error(Record ${id} for model type ${type.modelName} doesn't exist));

It would be nice if this error object also had a name property not-found making it easier to identify the specific error. 'not-found' would match the naming conventions of the FirestoreError class.

Fix toDate not existing when deserializing date

There was a check for this before which I probably removed by mistake while migrating files to TypeScript. Maybe due to unfortunate circumstances I didn't have regression tests to capture this. Will need to add it back and also add tests so this doesn't happen again.

model name in find doesn't match normal ED syntax

When fetching a model called blog-post in emberfire I used to call

this.store.findAll('blog-post');

But with this adapter i have to use:

this.store.findAll('blogPost');

Is this intentional?

Brendan

Support FastBoot authentication

Firebase auth uses JWT tokens for authentication, not cookies. JWT tokens are short live so if we pass that in to the server through cookies and verify it, there's a high chance that it's already expired.

Service workers might work although I haven't tried this solution yet.

Here's the flow of I think FastBoot auth could work:

  1. Have a service worker that intercepts fetch and modify to add the result of getIdToken()
  2. In FastBoot server, verify the token through verifyIdToken().
  3. Once the verification succeeds, we generate a custom token through createCustomToken().
  4. We then pass-in the custom token in the request header to Ember FastBoot (maybe in Authorization header?)
  5. In Ember FastBoot, we access the token through the header and use it in the ESA authenticator restore() hook. We'll be using signInWithCustomToken() to finally sign in the user.

This addon can support points 1 and 5 although I'm still contemplating if we should have 1 built-in. Points 2, 3, and 4 would have to be done in the FastBoot server which is out of our control.

Referencing sub-collections

One can build references and hence save into sub-collections like:

this.get('store').findRecord('user', 'user_a').then((user) => {
  this.get('store').createRecord('post', {
    body: 'New Post Body',
    title: 'New Post Title',
    author: user
  }).save({
    adapterOptions: {
      buildReference(db) {
        return db.collection('users').doc('user_b').collection('feeds');
      }
    }
  });
});

But if I set such a sub-collection doc as a relation - like in the above example instead of post -> user you would set user -> post - it will always be serialized as a root-collection (see https://github.com/rmmmp/ember-cloud-firestore-adapter/blob/master/addon/serializers/cloud-firestore.js#L91).

Is this intentional or a bug?

Ember tests are failing following upgrade

Hello! I'm attempting to upgrade one of our apps to Ember 3.26. The app itself seems to work okay but I am encountering some new errors that seem to be related to this add-on. For example, this test:

import { module, test } from 'qunit';
import { setupRenderingTest } from 'ember-qunit';
import { render } from '@ember/test-helpers';
import { hbs } from 'ember-cli-htmlbars';

module('Integration | Component | video-list-item', function (hooks) {
  setupRenderingTest(hooks);

  hooks.beforeEach(async function () {
    await this.owner
      .lookup('service:session')
      .authenticate('authenticator:oneday-firebase', (auth) => {
        return auth.signInWithEmailAndPassword(email, password);
      });
  });

  hooks.afterEach(async function () {
    await this.owner.lookup('service:session').invalidate();
  });

  test('it renders', async function (assert) {
    let store = this.owner.lookup('service:store');
    const video = await store.findRecord('video', '7Qdgy5xYVOErF86idWaz');
    this.set('video', video);

    await render(hbs`<VideoListItem @video={{this.video}} @index=1 />`);

    assert.dom('[data-test-video-list-item]').exists();
  });
});

Results in the following error, which I was not seeing prior to the upgrade:

Uncaught (in promise) Error: endAsync called with no preceding beginAsync call.

The above error is accompanied by the following warning:

WARNING: Async Request leaks detected. Add a breakpoint here and set `store.generateStackTracesForTrackedRequests = true;`to inspect traces for leak origins:
	 - DS: Handle Adapter#findHasMany of 'video' : 'stage'
	 - DS: Handle Adapter#findHasMany of 'video' : 'subject'

The component itself is pretty simple. The template renders some details of the Video model it is handed. This includes listing out the titles of any related Stages and Subjects. The model definition looks like this:

import Model, { attr, hasMany } from '@ember-data/model';

export default class VideoModel extends Model {
  @attr('string') title;

  @hasMany('stage', {
    isRealtime: true,
  })
  stages;

  @hasMany('subject', {
    isRealtime: true,
  })
  subjects;

  get heading1() {
    return this.title;
  }
}

What appears to be happening is that Ember Data is loading the relationships asynchronously and Ember's testing tools are not happy about it. I understand why, but I don't know what alternative strategy I could use.

I had a good chat with a colleague, his reaction was "why don't you just stub out the store and mock some data?" For the test above, that would be fine, but I have an acceptance test that gets the same errors and warnings:

import { module, test } from 'qunit';
import { visit, currentURL } from '@ember/test-helpers';
import { setupApplicationTest } from 'ember-qunit';

module('Acceptance | user can view list of videos', function (hooks) {
  setupApplicationTest(hooks);

  hooks.beforeEach(async function () {
    await this.owner
      .lookup('service:session')
      .authenticate('authenticator:oneday-firebase', (auth) => {
        return auth.signInWithEmailAndPassword(email, password);
      });
  });

  hooks.afterEach(async function () {
    await this.owner.lookup('service:session').invalidate();
  });

  test('visiting /videos', async function (assert) {
    await visit('/videos');
    assert.equal(currentURL(), '/videos', 'the videos route can be accessed');
    assert.dom('[data-test-simple-filter]').exists('the filter tools exist');
    assert.dom('[data-test-video-list]').exists('the list of videos exist');
  });
});

The Route being accessed by this acceptance test makes the following requests to the Store:

import Route from '@ember/routing/route';
import { inject as service } from '@ember/service';
 
export default class VideoRoute extends Route {
 @service store;
 
 async model() {
   const videos = await this.store.findAll('video', { reload: true });
   const model = videos.filter((v) => !!v.published);
   await this.store.findAll('stage', { reload: true });
   await this.store.findAll('subject', { reload: true });
   return model;
 }
}

Implicitly, this is testing that a) a user can authenticate via the Auth emulator, b) a request made by that user passes the Firebase security rules, c) the Firestore emulator returns what we need for rendering in the app, d) the relevant bits get rendered in the DOM. If I were to stub all that out, I'm not sure how valid that test would be. I certainly couldn't use it in a CI context.

This leaves me wondering how others are testing their Ember apps using this add-on, using the Firebase Emulator suite? Are there other strategies that I should be considering? Are there obvious problems with the tests I've included above that could be addressed? Do I need to use Ember testing for basic render testing and something else for end-to-end tests and CI integration? I'm I naive to think I can get end-to-end tests working in this way?

Any and all feedback is extremely welcome. Thanks!

Appears not to be fully compatible with fastboot.

@rmmmp , I have hit the following issue:

TypeError: _this4.get(...).firestore is not a function
    at /Users/alexmasita/External/Personal/projects/Ember-Apps/mobile-services/tmp/broccoli_persistent_filterautoprefixer_filter-output_path-xEKeiDYd.tmp/assets/addon-tree-output/ember-cloud-firestore-adapter/adapters/cloud-firestore.js:161:1
    at initializePromise (/Users/alexmasita/External/Personal/projects/Ember-Apps/mobile-services/tmp/broccoli_persistent_filterautoprefixer_filter-output_path-xEKeiDYd.tmp/assets/rsvp.js:418:1)
   ........There is more code below this truncated for brevity..........

This issue disappears when I put the following code in my route:

model() {
    if (get(this, 'fastboot.isFastBoot')) return;
    return get(this, 'store').findAll('vehicle');
}

It means their needs to be a fastboot check somewhere in the adapter that negates the need for this check. What do you think?

New API for writes

The new API will replace the current way to specify a path and batch writes for a more Cloud Firestore way.

Here's a sample

const user = this.store.createRecord('user', {
  age: 15,
});

user.save({
  adapterOptions: {
    buildReference(db) {
      return db.collection('admins');
    },

    include(batch, db) {
      const userMetaRef = db.collection('notifications').doc(user.get('id'));

      batch.set(userMetaRef, {
        numOfFriends: 0,
        numOfNotifications: 0
      });
    }
  }
});

firebase-app.js: Overwriting FirebaseError base field "name" can cause unexpected behavior.

Hi @mikkopaderes. Thanks for your continued awesome work on this. I am getting the above warning in my console when I implement the ESA authentication solution as seen in your guides here. Everything seems to work perfectly but that warning might signify a problem that might stump me later. Is this a known issue from your end?

You can see from the following screenshot: Console View that even with the warning, the session's isAuthenticated property is true and I am able to get session authenticated data.

I am also getting odd behaviour where on my (fastboot) app refresh, the session appears not to stay authenticated as it goes to the authentication page. I have enabled fastboot support according to the Ember Simple Authentication documentation by using only the cookie session store but to no avail. When I disable fastboot, the session is restored correctly. Perhaps you can advise me on this as well if you can?

Let me know.

Write fails if batchSize limit is exceeded

Just wanted to point out that currently a batch defined via the include option which holds more changes than 500 write operations (which is the limit defined by Google Firestore) will cause the whole write operation to fail.

One option would be to split all batched mutations up into multiple batches of 500 and finally invoke their commit.

Add support for static data by default

By default, the adapter listens for real-time changes. I've done this by default just to keep the similarities with Firebase Realtime DB. I've since realized then that listening for real-time changes should be opt-in.

Should we make real-time updates opt-in, this will introduce a breaking change.

Namespace at the adapter/serializer level?

First of all, thank you very much for this addon. It's exactly what I was looking for after revisiting Emberfire and finding it in WIP and basically abandoned state. I love the lightweight, flexible, no-nonsense approach you took.

I'm looking for a way to "namespace" the default read/write operations at the adapter/serializer level. My use case would be similar to the emberfire namespace attribute on the firestore adapter. For example if I had a model "item" and wanted the "items" collection to be written to/read from at environments/<build env>/users/<uid>/items instead of just root items it would be very nice to just have to specify a namespace in one place like the adapter e.g. something like

  get namespace() {
    return `/environments/${config.environment}/users/${this.currentUser.userId}`;
  }

Is this possible currently? And if not would you be open to supporting something like this? I'd be happy to contribute a PR if so.

[bug]: EMFILE: too many open files, watch

We are getting the following error after installing this Addon: EMFILE: too many open files, watch.

It's replicable across multiple machines.

Is this something you've encountered before?

FirebaseAuthenticator: onAuthStateChanged User object is occasionally null when multiple tabs open

I've found that the User object passed by onAuthStateChanged() in the restore() method of FirebaseAuthenticator is sometimes null when multiple instances of the Ember app are running.

Steps to recreate:

  • Open two instances of your app (can be multiple tabs or multiple windows)
  • Log the session data object in the post-authentication route
  • Sign in on the first tab:
await this.session.authenticate('authenticator:firebase', (auth) => {
  return auth.signInWithEmailAndPassword(email, password);
});
  • Visit the second tab

This screenshot is from the second tab after logging into the first:

firebase

It seems to be some sort of timing issue. Logging this.session.data.authenticated.user in the immediate post-authentication route produces null about half of the time. I can then refresh the page, and then the restore() method is able to find the session data (and the user object) again.

firebase2

Any ideas?

New hasMany API

Much idea similar here. Like to implement. Need to test feasibility first. Snippet below.

import { hasMany } from 'ember-data/relationships';
import Model from 'ember-data/model';
import attr from 'ember-data/attr';

export default Model.extend({
  age: attr('number'),
  username: attr('string'),
  friends: hasMany('user', {
    filter(reference, record) {
      return reference.where('age', '>=', record.get('age')).limit(10);
    }
  }),
});

Here's how to change the relationship option dynamically for use-cases such as changing filter hook to increase the limit for infinite scrolling/pagination.

Disclaimer: I'm not sure if changing the relationship option has any drawbacks in Ember Data. I assume that there's none as long as you don't change built-in options such as async, inverse, etc..

const user = await this.store.findRecord('user', 'user_a');
const friends = await user.get('friends');
const newLimit = 20;

friends.relationship.relationshipMeta.options.filter = (reference, record) => {
  return reference.where('age', '>=', record.get('age')).limit(newLimit);
};
friends.reload();

Not working in FastBoot

Shows the following error

Function Firestore.settings() requires its first argument to be of type object, but it was: a custom Object object

Not sure why it's getting picked up as a custom Object object. Even though I've just hard coded it to pass-in {}, new Object(), Object.assign({}), it doesn't work.

I'm inclined to think that this is a Firebase issue but I'm unable to replicate it in a Node environment. If all else fail, my workaround would be to let the deprecation in #43 reappear and wait until we no longer need to execute Firebase.settings() due to timestampsInSnapshots defaulting to true in the future.

EDIT:

Workaround would be to set firestoreSettings: null in your adapter. As a result, the Firestore deprecation will show up.

fails to build when using pods structure

Build failed.
Build Error (Concat)

ENOENT: no such file or directory, open '/Users/xxxxxxxx/development/cfireview/clean-pods/tmp/source_map_concat-input_base_path-i7wWgn1z.tmp/node_modules/firebase/firebase-firestore.js'`


ENV Summary:

  TIME: Sat Jan 27 2018 20:54:24 GMT-0500 (EST)
  TITLE: ember
  ARGV:
  - /Users/xxxxxxxx/.nvm/versions/node/v8.9.4/bin/node
  - /usr/local/bin/ember
  - build
  EXEC_PATH: /Users/xxxxxxxx/.nvm/versions/node/v8.9.4/bin/node
  TMPDIR: /var/folders/y_/z1hh7_lj6vd814ycqvqp6g2c0000gn/T
  SHELL: /bin/zsh
  PATH:
  - /Users/xxxxxxxx/.nvm/versions/node/v8.9.4/bin
  - /Users/xxxxxxxx/anaconda3/bin
  - /Users/xxxxxxxx/.dnx/bin
  - /usr/local/heroku/bin
  - /Users/xxxxxxxx/.rbenv/shims
  - /Users/xxxxxxxx/bin
  - /usr/local/bin
  - /usr/bin
  - /bin
  - /usr/sbin
  - /sbin
  PLATFORM: darwin x64
  FREEMEM: 90284032
  TOTALMEM: 17179869184
  UPTIME: 119661
  LOADAVG: 2.96044921875,2.6064453125,2.76611328125
  CPUS:
  - Intel(R) Core(TM) i7-4980HQ CPU @ 2.80GHz - 2800
  - Intel(R) Core(TM) i7-4980HQ CPU @ 2.80GHz - 2800
  - Intel(R) Core(TM) i7-4980HQ CPU @ 2.80GHz - 2800
  - Intel(R) Core(TM) i7-4980HQ CPU @ 2.80GHz - 2800
  - Intel(R) Core(TM) i7-4980HQ CPU @ 2.80GHz - 2800
  - Intel(R) Core(TM) i7-4980HQ CPU @ 2.80GHz - 2800
  - Intel(R) Core(TM) i7-4980HQ CPU @ 2.80GHz - 2800
  - Intel(R) Core(TM) i7-4980HQ CPU @ 2.80GHz - 2800
  ENDIANNESS: LE
  VERSIONS:
  - ares: 1.10.1-DEV
  - cldr: 31.0.1
  - http_parser: 2.7.0
  - icu: 59.1
  - modules: 57
  - nghttp2: 1.25.0
  - node: 8.9.4
  - openssl: 1.0.2n
  - tz: 2017b
  - unicode: 9.0
  - uv: 1.15.0
  - v8: 6.1.534.50
  - zlib: 1.2.11

ERROR Summary:

  - broccoliBuilderErrorStack: Error: ENOENT: no such file or directory, open '/Users/xxxxxxxx/development/cfireview/clean-pods/tmp/source_map_concat-input_base_path-i7wWgn1z.tmp/node_modules/firebase/firebase-firestore.js'
    at Object.fs.openSync (fs.js:646:18)
    at Object.fs.readFileSync (fs.js:551:33)
    at SourceMap.addFile (/Users/xxxxxxxx/development/cfireview/clean-pods/node_modules/fast-sourcemap-concat/lib/source-map.js:75:31)
    at /Users/xxxxxxxx/development/cfireview/clean-pods/node_modules/broccoli-concat/concat.js:200:16
    at Array.forEach (<anonymous>)
    at Concat.<anonymous> (/Users/xxxxxxxx/development/cfireview/clean-pods/node_modules/broccoli-concat/concat.js:198:24)
    at /Users/xxxxxxxx/development/cfireview/clean-pods/node_modules/fast-sourcemap-concat/lib/source-map.js:419:12
    at initializePromise (/Users/xxxxxxxx/development/cfireview/clean-pods/node_modules/rsvp/dist/rsvp.js:567:5)
    at new Promise (/Users/xxxxxxxx/development/cfireview/clean-pods/node_modules/rsvp/dist/rsvp.js:1039:33)
    at SourceMap.end (/Users/xxxxxxxx/development/cfireview/clean-pods/node_modules/fast-sourcemap-concat/lib/source-map.js:406:10)
  - codeFrame: ENOENT: no such file or directory, open '/Users/xxxxxxxx/development/cfireview/clean-pods/tmp/source_map_concat-input_base_path-i7wWgn1z.tmp/node_modules/firebase/firebase-firestore.js'
  - errorMessage: Build Canceled: Broccoli Builder ran into an error with `Concat` plugin. πŸ’₯
ENOENT: no such file or directory, open '/Users/xxxxxxxx/development/cfireview/clean-pods/tmp/source_map_concat-input_base_path-i7wWgn1z.tmp/node_modules/firebase/firebase-firestore.js'
  - errorType: Build Error
  - location:
    - column: [undefined]
    - file: [undefined]
    - line: [undefined]
    - treeDir: [undefined]
  - message: Build Canceled: Broccoli Builder ran into an error with `Concat` plugin. πŸ’₯
ENOENT: no such file or directory, open '/Users/xxxxxxxx/development/cfireview/clean-pods/tmp/source_map_concat-input_base_path-i7wWgn1z.tmp/node_modules/firebase/firebase-firestore.js'
  - name: Error
  - nodeAnnotation: Concat
  - nodeName: Concat
  - originalErrorMessage: ENOENT: no such file or directory, open '/Users/xxxxxxxx/development/cfireview/clean-pods/tmp/source_map_concat-input_base_path-i7wWgn1z.tmp/node_modules/firebase/firebase-firestore.js'
  - stack: Error: ENOENT: no such file or directory, open '/Users/xxxxxxxx/development/cfireview/clean-pods/tmp/source_map_concat-input_base_path-i7wWgn1z.tmp/node_modules/firebase/firebase-firestore.js'
    at Object.fs.openSync (fs.js:646:18)
    at Object.fs.readFileSync (fs.js:551:33)
    at SourceMap.addFile (/Users/xxxxxxxx/development/cfireview/clean-pods/node_modules/fast-sourcemap-concat/lib/source-map.js:75:31)
    at /Users/xxxxxxxx/development/cfireview/clean-pods/node_modules/broccoli-concat/concat.js:200:16
    at Array.forEach (<anonymous>)
    at Concat.<anonymous> (/Users/xxxxxxxx/development/cfireview/clean-pods/node_modules/broccoli-concat/concat.js:198:24)
    at /Users/xxxxxxxx/development/cfireview/clean-pods/node_modules/fast-sourcemap-concat/lib/source-map.js:419:12
    at initializePromise (/Users/xxxxxxxx/development/cfireview/clean-pods/node_modules/rsvp/dist/rsvp.js:567:5)
    at new Promise (/Users/xxxxxxxx/development/cfireview/clean-pods/node_modules/rsvp/dist/rsvp.js:1039:33)
    at SourceMap.end (/Users/xxxxxxxx/development/cfireview/clean-pods/node_modules/fast-sourcemap-concat/lib/source-map.js:406:10)

=================================================================================

Unhandled Exception on Delete: "Assertion Failed: You can only unload a record which is not inFlight.

Exception: "Assertion Failed: You can only unload a record which is not inFlight.
My code:

        obj.destroyRecord({
          adapterOptions: {
            isRealtime: true,
            buildReference(db) {
              return db.collection('collection1').doc(obj_parent.get('id')).collection('sub_collection1');
            }
          }
        });

My guess is that the firestore adapter unloads the obj before the server acknowledged the delete.

Stack Trace:

"Error: Assertion Failed: You can only unload a record which is not inFlight. `<obj:9k2j2ogWWg5PybdAkDO9>`
    at new EmberError (http://localhost:4200/assets/vendor.js:18152:31)
    at Object.assert (http://localhost:4200/assets/vendor.js:17017:23)
    at Object.assertAgainstUnloadRecord [as unloadRecord] (http://localhost:4200/assets/vendor.js:105326:30)
    at InternalModel.send (http://localhost:4200/assets/vendor.js:111422:30)
    at InternalModel.unloadRecord (http://localhost:4200/assets/vendor.js:111175:10)
    at Class.unloadRecord (http://localhost:4200/assets/vendor.js:106281:25)
    at Class.unloadRecord (http://localhost:4200/assets/vendor.js:114422:12)
    at Object.docRef.onSnapshot.docSnapshot [as next] (http://localhost:4200/assets/vendor.js:95498:19)
    at next (http://localhost:4200/assets/vendor.js:66261:351333)
    at http://localhost:4200/assets/vendor.js:66261:323998"

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.