Skip to content

Typings Refactor #334

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 38 commits into from
Dec 11, 2017
Merged

Typings Refactor #334

merged 38 commits into from
Dec 11, 2017

Conversation

jshcrowthe
Copy link
Contributor

This PR refactors the single top level firebase package curated types into a series of 6 "types packages."

They are:

  • @firebase/app-types
  • @firebase/auth-types
  • @firebase/database-types
  • @firebase/firestore-types
  • @firebase/messaging-types
  • @firebase/storage-types

Each of these "types packages" defines the curated public interface and is used to generate our typings for the rest of the SDK.

Each component is encouraged to implement/extend these interfaces in their implementation directly (see the firestore package for a great example of this being done consistently throughout a codebase). Allowing the type system to validate that we are in fact exposing the interface we think we are.

Review Guide

All reviewers should take a look at the following commits as it pertains to their component (i.e. look at packages/app, packages/<COMPONENT>, packages/<COMPONENT>-types, and packages/firebase/<COMPONENT> in each of these commits:

  • d85410f - @firebase/app type refactor (@firebase/auth reviewer can skip this section)
  • 9786607 - Adding the typings fields
  • 51cff8d - Adding type package deps
  • 086971e - Changes to the top level package

Broken down for each package owner are the package specific changes that were made:

Auth

Open to see relevant changes
  • 26ba65f - Initial changes to auth structure
  • c7e9892 - Adding the @firebase/auth types

Database

Open to see relevant changes
  • f16c975 - Initial changes to database structure
  • 0c93766 - Adding the @firebase/database types

Firestore

Open to see relevant changes
  • 5b334fc - Initial changes to firestore structure
  • 244ae14 - Adding the @firebase/firestore types

Messaging

Open to see relevant changes
  • 376d80c - Initial changes to messaging structure
  • aa1f7b0 - Adding the @firebase/messaging types

Storage

Open to see relevant changes
  • fd26887 - Initial changes to storage structure
  • 30e8354 - Adding the @firebase/storage types

There are a couple of cosmetic commits in there and a bugfix or two where an approach wasn't doing what I needed to do. Those commits don't actually change any code in what I have highlighted but move it to locations where everything works as expected.

Testing Flow

I manually validated these changes doing the following:

  1. Run the following in your terminal (at the root of the package):
    $(npm bin)/lerna exec --scope @firebase/* --scope firebase -- yarn link

  2. Create a separate sandbox directory to test changes

  3. Run yarn link <COMPONENT> for every component I intend to test

    e.g.

    $ yarn link @firebase/app
    $ yarn link @firebase/messaging
    
  4. Create a stub .ts file and import the components I linked.

  5. Exercise the API as I'd expect it to work.

Testing

Additionally (and I will continue adding to this), I have added a test folder to each @fireabse/*-types package. In this I can exercise the API as I'd expect and ensure that the file properly compiles. This tests the validity of the typings with regards to our expected use cases. These files will never actually be run so I don't need to make them semantically correct, we simply are using them to test all expected use cases (this is how the typings package DefinitelyTyped tests the typings for each respective package.

Copy link
Contributor

@mikelehen mikelehen left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I looked through the Firestore changes and overall I'm very happy with the way this turned out. I flagged a few minor-ish things (and one major thing that's probably responsible for firestore tests failing).

I didn't actually test / verify anything yet, but I'll do so tomorrow.

};

export class FirebaseApp {
constructor();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We don't support instantiating FirebaseApp directly, do we? So this should be private? (here and elsewhere... I see FirebaseAuth lists a public constructor too)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I pulled the public constructors from all the top level classes. Didn't realize they could be omitted.

/**
* Create (and intialize) a FirebaseApp.
*
* @param options Options to configure the services use in the App.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

use => used

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

*
* @param options Options to configure the services use in the App.
* @param name The optional name of the app to initialize ('[DEFAULT]' if
* none)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"if none" sounds weird to me. Perhaps "if omitted" (here and elsewhere)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

* For testing FirebaseApp instances:
* app() instanceof firebase.app.App
*
* DO NOT call this constuctor directly (use firebase.app() instead).
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Firestore has hackery to prevent people from calling our constructors (see

export function makeConstructorPrivate<T>(cls: T, optionalMessage?: string): T {
)

Perhaps we should do the same (and then you don't need the comment warning, since it just won't work)?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This would be a change to how the other APIs function currently, lets keep this option on the table though.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure what you mean. What would be the change?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Calling the PublicFirestore constructor directly results in an Error being thrown, whereas the other components provide valid constructors to the firebase.. path.

It's a way to prevent people from instantiating the class themselves, but it is a change in the current behavior of the SDK. We do specify in our documentation that the constructors (presumably valid) exist at that path even though we say not to use it.

Either way, if this is a change we really want to pursue, I'd say let's do it in follow up rather than in this PR.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Okay, I'm fine with that. I just think it's ugly to have a big scary warning in the comments / docs when we could just disallow it... and since it's already documented as not supported, I wouldn't consider this a breaking change or anything. But I'm fine with punting on it for later. Firestore already does it, which is all I really care about. :-)

// es6-promises.
Promise: typeof Promise;

// The current SDK version ('${JSCORE_VERSION}').
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think JSCORE_VERSION will get replaced here, right?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good call, I'll pull that.

const _name: string = app.name;
const _options: Object = app.options;
const _delete: Promise<any> = app.delete();
});
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not exactly sure what is going on with this "test" file... Since we're not actually asserting anything, I think this is just compile-time verification (do we even execute this file? or just compile it?)... So it's basically a way for us to write against the .d.ts and sort of sanity-check that the type definitions are really what we think they are?

I find the value of this somewhat questionable (it doesn't provide much extra value than just reviewing the .d.ts directly), but if that is the intent, perhaps we should at least add a comment to the top of the file explaining the intent and perhaps change the "Assert that" wording, since there are no assertions here.

Note that if the intention is to verify that the runtime behavior matches our type definitions, I don't think it's very successful since e.g. there won't be any apps and so this forEach callback won't be run, and even if it was, if there was a mistake in the FirebaseApp typings (e.g. there isn't actually a .name property) this code likely wouldn't detect it since it's not asserting anything... (similarly the firebase.SDK_VERSION regex isn't asserted to pass, so it wouldn't test anything, etc.)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As I mentioned in the PR description, this is the "testing" that is done in the DefinitelyTyped repo (the only other repo I'm aware of that actively does curated types). I'll pull the wording, but the purpose of this file (and the others that need to be fleshed out) is to illustrate expected valid use cases of the API and make sure that they are considered type-safe.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, sorry! FWIW, I think this makes more sense for DefinitivelyTyped than it does for us, since we (should) have real tests that run against the implementation and we should be able to compile those tests against the types (as Firestore does). So I would not be in favor of ever writing these sorts of compile-only tests against Firestore, since we already get that validation from our tests, so it'd just be an unnecessary maintenance burden. But I am fine with other packages relying on these tests if they want to...

* limitations under the License.
*/

import { firebase } from '@firebase/app';
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this file different than default.d.ts?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, I am testing the default export vs. the named firebase export. Tested paths are identical but it's a different entrypoint.

@@ -155,7 +156,9 @@ export class FirebaseCredentialsProvider implements CredentialsProvider {
this.userCounter = 0;

// Will fire at least once where we set this.currentUser
this.app.INTERNAL.addAuthTokenListener(this.tokenListener);
(this.app as _FirebaseApp).INTERNAL.addAuthTokenListener(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you change app to be typed as _FirebaseApp and do the cast once at assignment time?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I opted to do it case-by-case as we don't need to access the INTERNAL namespace often regardless. Ultimately it will be going away so I'd rather leave the individual casts just as "REFACTOR THIS" flags 😄

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, interesting. When / why exactly will this be going away?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The INTERNAL namespace was used for code sharing. We are going to do something formal in this area to remove that need. It's not immediate, but soon.

@@ -131,7 +131,10 @@ export class GrpcConnection implements Connection {
log.debug(LOG_TAG, 'Creating Firestore stub.');
const credentials = createHeaders(this.databaseInfo, token);
this.cachedStub = {
stub: new this.firestore.Firestore(this.databaseInfo.host, credentials),
stub: new this.firestore.FirebaseFirestore(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is not correct, and tests should be failing!


export type LogLevel = 'debug' | 'error' | 'silent';

export function setLogLevel(logLevel: LogLevel): void;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It feels weird to be exporting a setLogLevel function (there is no "setLogLevel" function anywhere that you can directly import), but I think this whole firestore-types d.ts file is essentially an implementation detail (the "real" exports are in index.ts /index.node.ts) and so I guess this is fine, though looks a bit strange.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

@@ -23,4 +23,4 @@ import firebase from '@firebase/app';

// TODO(b/66917182): This "as any" removes all of our type-checking in tests and
// is therefore pretty bad. But I can't figure out how to avoid it right now.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you remove the comment too? :-)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Uhhh yes. (Derp moment... 😉 )

Copy link
Contributor

@schmidt-sebastian schmidt-sebastian left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I added my comments for Database directly in the corresponding commits:

f16c975
0c93766

Thanks!

@schmidt-sebastian
Copy link
Contributor

Good to go for the Database.

I do think we should go through and pick better parameter names for the callback types (result: error:) seems better than the existing (a: , b:), but this can be a separate effort.

applyActionCode(code: string): Promise<any>;
checkActionCode(code: string): Promise<any>;
confirmPasswordReset(code: string, newPassword: string): Promise<any>;
createUserWithEmailAndPassword(email: string, password: string): Promise<any>;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you add the following 4 methods that are being released this week:
createUserAndRetrieveDataWithEmailAndPassword(email: string, password: string): Promise<any>;

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

): Promise<any>;
setPersistence(persistence: Persistence): Promise<any>;
signInAndRetrieveDataWithCredential(credential: AuthCredential): Promise<any>;
signInAnonymously(): Promise<any>;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add:
signInAnonymouslyAndRetrieveData(): Promise<any>;

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

signInAndRetrieveDataWithCredential(credential: AuthCredential): Promise<any>;
signInAnonymously(): Promise<any>;
signInWithCredential(credential: AuthCredential): Promise<any>;
signInWithCustomToken(token: string): Promise<any>;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add:
signInAndRetrieveDataWithCustomToken(token: string): Promise<any>;

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

signInAnonymously(): Promise<any>;
signInWithCredential(credential: AuthCredential): Promise<any>;
signInWithCustomToken(token: string): Promise<any>;
signInWithEmailAndPassword(email: string, password: string): Promise<any>;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add:
signInAndRetrieveDataWithEmailAndPassword(email: string, password: string): Promise<any>;

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

}

declare module '@firebase/app-types' {
interface FirebaseNamespace {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have updated the typings and added the classes mentioned, PTAL.

Copy link
Contributor

@sphippen sphippen left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Storage looks good.

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

7 participants