Skip to content

Revive Node.JS Support for Cloud Firestore (fixes #221). #319

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 7 commits into from
Nov 20, 2017

Conversation

mikelehen
Copy link
Contributor

  • Fix miscellaneous node / GRPC bit rot.
  • Fix WebChannel / GRPC Connection compatibility issues.
    • Add an explicit mapping from "RPC name" (e.g. "BatchGetDocuments") to the
      REST url path (e.g. "batchGet") for WebChannel, and for GRPC just assume
      the first letter should be lowercased (e.g. "batchGetDocuments").
    • Split Connection.invoke() into invokeRPC() and invokeStreamingRPC(), with
      the latter accepting a stream of results and aggregating them into an
      array (needed to support BatchGetDocuments RPC).
  • Fix serializer issues
    • Query limits are an 'Int32Value' but we were serializing them as a normal
      int which GRPC / protobufjs didn't like.
    • Several proto "oneof tags" were outdated.
  • Add build steps to copy protos into npm package.
  • Run integration tests for Node.JS
    • Added to 'test:node' script in package.json and in .vscode/launch.json
    • Include index.ts for browser and index.node.ts for node so the appropriate
      PlatformSupport gets registered.
  • Misc cleanup
    • Remove unused MockPlatform since we use the normal NodePlatform now.
    • Remove 'google-auth-library' CredentialsProvider that we used to use for
      node.js (before we were integrated with FirebaseAuth).
    • Fixed several tests that were hitting node.js warnings about unhandled
      promise failures.
    • mocha commmand-line args:
      • "--compilers ts:ts-node/register" was deprecated in favor of
        "--require ts-node/register"
      • Remove "--retries 5" when running mocha tests from VS Code.
      • Consistently use "--require" instead of "-r"
      • Add "--exit" when running from VS Code.

* Fix miscellaneous node / GRPC bit rot.
  * Re-add grpc / protobufjs dependencies.
  * Remove crazy grpc stream error handling nonsense that is no longer
    necessary after grpc/grpc#9101
  * Clean up grpc_connection logging (and consistently use util.inspect()).
* Fix WebChannel / GRPC Connection compatibility issues.
  * Add an explicit mapping from "RPC name" (e.g. "BatchGetDocuments") to the
    REST url path (e.g. "batchGet") for WebChannel, and for GRPC just assume
    the first letter should be lowercased (e.g. "batchGetDocuments").
  * Split Connection.invoke() into invokeRPC() and invokeStreamingRPC(), with
    the latter accepting a stream of results and aggregating them into an
    array (needed to support BatchGetDocuments RPC).
* Fix serializer issues
  * Query limits are an 'Int32Value' but we were serializing them as a normal
    int which GRPC / protobufjs didn't like.
  * Several proto "oneof tags" were outdated.
* Add build steps to copy protos into npm package.
* Run integration tests for Node.JS
  * Added to 'test:node' script in package.json and in .vscode/launch.json
  * Include index.ts for browser and index.node.ts for node so the appropriate
    PlatformSupport gets registered.
* Misc cleanup
  * Remove unused MockPlatform since we use the normal NodePlatform now.
  * Remove 'google-auth-library' CredentialsProvider that we used to use for
    node.js (before we were integrated with FirebaseAuth).
  * Fixed several tests that were hitting node.js warnings about unhandled
    promise failures.
  * mocha commmand-line args:
    * "--compilers ts:ts-node/register" was deprecated in favor of
      "--require ts-node/register"
    * Remove "--retries 5" when running mocha tests from VS Code.
    * Consistently use "--require" instead of "-r"
    * Add "--exit" when running from VS Code.
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'm so happy.

Supposedly WebStorm EAP will allow us to run tests from the IDE. I'm still running into errors, but determined to make this work once this is merged.

@@ -18,7 +18,9 @@
"module": "dist/esm/index.js",
"license": "Apache-2.0",
"dependencies": {
"@firebase/webchannel-wrapper": "^0.2.4"
"@firebase/webchannel-wrapper": "^0.2.4",
"grpc": "^1.6.6",
Copy link
Contributor

Choose a reason for hiding this comment

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

Did you try to run these tests with the latest versions of these dependencies?

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 now... It turns out grpc now has typings defined so we could remove our hacky ones (I also had to tweak a few things to make our code compatible with the defined typings).

@@ -339,6 +333,8 @@ export class WebChannelConnection implements Connection {

// visible for testing
makeUrl(rpcName: string): string {
const urlRpcName = RPC_NAME_REST_MAPPING[rpcName];
assert(urlRpcName != null, 'Unknown REST mapping for: ' + rpcName);
Copy link
Contributor

Choose a reason for hiding this comment

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

This should be !== undefined.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

It can be !== undefined but I tend to not differentiate "undefined" and "null" when I don't need to, so I did the more inclusive != null check. But I also don't care in the slightest... So switched. :-)


// RPC Methods have the first character lower-cased
// (e.g. Listen => listen(), BatchGetDocuments => batchGetDocuments()).
const rpcMethod = rpcName.charAt(0).toLowerCase() + rpcName.slice(1);
Copy link
Contributor

Choose a reason for hiding this comment

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

Can we use the same manually-provided mapping that we use in webchannel?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

No... the mapping is different because the REST endpoints are manually defined (you can see them in firestore.proto if you want). So e.g. the BatchGetDocuments RPC shows up as .batchGetDocuments() in GRPC but as .../batchGet in the REST URL.

Copy link
Contributor

Choose a reason for hiding this comment

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

Sorry, I wasn't more clear. I meant the mapping technique (a hardcoded map instead of this on-the-fly conversion).

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 like the on-the-fly conversion since it means we don't have to hardcode all of the RPCs we use. With WebChannel we can't avoid the hardcoding since the mapping isn't deterministic (whereas I /think/ it is for GRPC).

// trigger the 'finish' event).
remoteEnded = true;
grpcStream.end();
close();
Copy link
Contributor

@schmidt-sebastian schmidt-sebastian Nov 16, 2017

Choose a reason for hiding this comment

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

This now calls the onClose callback, whereas before it didn't. This seems like a requirement to let the Write/Watch stream know that it ended, but I wanted to make sure this is intentional.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yeah. We used to call grpcStream.end() which would eventually call 'finish' which would call close() [it was convoluted to work around GRPC error handling bugs]. Now we do it directly.

);
});

grpcStream.on('status', (status: GrpcStatus) => {
if (!closed) {
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 obsolete now, isn't it?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Woops, good catch. Yes. I meant to remove this. (done)

rpcName: string,
request: any,
token: Token | null
): 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 this be of type 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.

Sure can! (done)

@@ -20,6 +20,11 @@ import { DatabaseId, DatabaseInfo } from '../../../src/core/database_info';
import * as utilHelpers from '../util/helpers';

describe('WebChannel', () => {
Copy link
Contributor

Choose a reason for hiding this comment

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

Can we do something like: (typedef window | describe ? xdescribe)('WebChannel', ...

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Apparently (according to https://mochajs.org/), the correct way to skip tests programmatically is:

before(function() {
  if (/* check test environment */) {
    // setup code
  } else {
    this.skip();
  }
});

But I tried that and it was kinda' crappy (you have to use function() { ... } instead of () => { ... } for this to work, and it only skips the top-level tests, not the nested describe() tests.... So I did it your way. :-)

Copy link
Contributor Author

@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.

Thanks! PTAL.

@@ -18,7 +18,9 @@
"module": "dist/esm/index.js",
"license": "Apache-2.0",
"dependencies": {
"@firebase/webchannel-wrapper": "^0.2.4"
"@firebase/webchannel-wrapper": "^0.2.4",
"grpc": "^1.6.6",
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 now... It turns out grpc now has typings defined so we could remove our hacky ones (I also had to tweak a few things to make our code compatible with the defined typings).

@@ -339,6 +333,8 @@ export class WebChannelConnection implements Connection {

// visible for testing
makeUrl(rpcName: string): string {
const urlRpcName = RPC_NAME_REST_MAPPING[rpcName];
assert(urlRpcName != null, 'Unknown REST mapping for: ' + rpcName);
Copy link
Contributor Author

Choose a reason for hiding this comment

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

It can be !== undefined but I tend to not differentiate "undefined" and "null" when I don't need to, so I did the more inclusive != null check. But I also don't care in the slightest... So switched. :-)


// RPC Methods have the first character lower-cased
// (e.g. Listen => listen(), BatchGetDocuments => batchGetDocuments()).
const rpcMethod = rpcName.charAt(0).toLowerCase() + rpcName.slice(1);
Copy link
Contributor Author

Choose a reason for hiding this comment

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

No... the mapping is different because the REST endpoints are manually defined (you can see them in firestore.proto if you want). So e.g. the BatchGetDocuments RPC shows up as .batchGetDocuments() in GRPC but as .../batchGet in the REST URL.

// trigger the 'finish' event).
remoteEnded = true;
grpcStream.end();
close();
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yeah. We used to call grpcStream.end() which would eventually call 'finish' which would call close() [it was convoluted to work around GRPC error handling bugs]. Now we do it directly.

);
});

grpcStream.on('status', (status: GrpcStatus) => {
if (!closed) {
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Woops, good catch. Yes. I meant to remove this. (done)

rpcName: string,
request: any,
token: Token | null
): 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.

Sure can! (done)

@@ -20,6 +20,11 @@ import { DatabaseId, DatabaseInfo } from '../../../src/core/database_info';
import * as utilHelpers from '../util/helpers';

describe('WebChannel', () => {
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Apparently (according to https://mochajs.org/), the correct way to skip tests programmatically is:

before(function() {
  if (/* check test environment */) {
    // setup code
  } else {
    this.skip();
  }
});

But I tried that and it was kinda' crappy (you have to use function() { ... } instead of () => { ... } for this to work, and it only skips the top-level tests, not the nested describe() tests.... So I did it your way. :-)

): ProtobufProtoBuilder | undefined {
const builder = protobufjs.newBuilder({
export function loadProtos(): grpc.GrpcObject {
const options = {
// Beware that converting fields to camel case does not convert the tag
// fields in oneof groups (!!!).
Copy link
Contributor

Choose a reason for hiding this comment

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

Does this refer to valueType? ProtobufJS 6.x+ is now doing this.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Interesting. I tried to go down the route of depending directly on protobufjs again and upgrading to 6.x, but it did not seem straightforward (the entire way we're consuming protobufjs has changed an doesn't seem to be documented in the release notes, etc.). So I'm punting on this for now. I did update the comment to mention that upgrading to protobufjs 6.x would resolve this though.

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.

Please look at the comment in grpc_connection.ts

Copy link
Contributor Author

@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.

Thanks!


// RPC Methods have the first character lower-cased
// (e.g. Listen => listen(), BatchGetDocuments => batchGetDocuments()).
const rpcMethod = rpcName.charAt(0).toLowerCase() + rpcName.slice(1);
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 like the on-the-fly conversion since it means we don't have to hardcode all of the RPCs we use. With WebChannel we can't avoid the hardcoding since the mapping isn't deterministic (whereas I /think/ it is for GRPC).

): ProtobufProtoBuilder | undefined {
const builder = protobufjs.newBuilder({
export function loadProtos(): grpc.GrpcObject {
const options = {
// Beware that converting fields to camel case does not convert the tag
// fields in oneof groups (!!!).
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Interesting. I tried to go down the route of depending directly on protobufjs again and upgrading to 6.x, but it did not seem straightforward (the entire way we're consuming protobufjs has changed an doesn't seem to be documented in the release notes, etc.). So I'm punting on this for now. I did update the comment to mention that upgrading to protobufjs 6.x would resolve this though.

@mikelehen mikelehen merged commit 087375c into master Nov 20, 2017
@mikelehen mikelehen deleted the mikelehen-node-support branch November 20, 2017 19:37
@kylehotchkiss
Copy link

Hello! When will this be launched in the firebase package on NPM?

@mikelehen
Copy link
Contributor Author

Likely this Thursday. You can keep an eye on https://firebase.google.com/support/release-notes/js to see when it goes out.

@jmensch1
Copy link

hey thanks for the update! I think there's a problem though. I upgraded to 4.7.0 with npm, and now when I initialize firestore I'm getting the error 'firebase.firestore is not a function'

My imports are the same they were before:

import * as firebase from 'firebase'
import 'firebase/firestore'

And I initialize firestore by calling firebase.firestore()

@jshcrowthe
Copy link
Contributor

jshcrowthe commented Nov 30, 2017

@jmensch1 feel free to open an issue with more context (hopefully a sample repro) of the behavior you are seeing. I was unable to reproduce (just verified across a node build and a browser build).

@firebase firebase locked and limited conversation to collaborators Oct 24, 2019
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

7 participants