Skip to content

Commit

Permalink
refactor(database): Add typescript database implementation [Part 2/3] (
Browse files Browse the repository at this point in the history
…#62)

* refactor(database): add typescript implementation

* refactor(database): update build process to include database.ts

All integration tests pass at this point

* refactor(*): refactor environment builds to be based on separate .ts files

* WIP: patch database code in nodeJS

* refactor(database): classes for typescript database implementation (#55)

* refactor(database): classes for typescript database implementation

* refactor(database): requested changes & other improvements

* fix(database): Add missing "typeof" (#74)

https://github.com/firebase/firebase-js-sdk/blob/fd0728138d88c454f8e38a78f35d831d6365070c/src/database/js-client/core/Repo.js#L86

* WIP: fixes from @schmidt-sebastian's review

* WIP: fix: TS Build error

* fix(database): fix issue with missing repo method

* WIP: review adjustments #1

* WIP: review comments #2
  • Loading branch information
jshcrowthe authored Jun 28, 2017
1 parent bbd86c1 commit bb80d31
Show file tree
Hide file tree
Showing 107 changed files with 17,934 additions and 111 deletions.
1 change: 0 additions & 1 deletion gulp/config.js
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,6 @@ module.exports = {
babel: {
plugins: [
require('babel-plugin-add-module-exports'),
require('babel-plugin-minify-dead-code-elimination')
],
presets: [
[require('babel-preset-env'), {
Expand Down
58 changes: 12 additions & 46 deletions gulp/tasks/build.js
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,7 @@ function compileIndvES2015ModulesToBrowser() {
'firebase-app': './src/app.ts',
'firebase-storage': './src/storage.ts',
'firebase-messaging': './src/messaging.ts',
'firebase-database': './src/database.ts',
},
output: {
path: path.resolve(__dirname, './dist/browser'),
Expand Down Expand Up @@ -192,27 +193,6 @@ function compileIndvES2015ModulesToBrowser() {
.pipe(gulp.dest(`${config.paths.outDir}/browser`));
}

function compileSDKES2015ToBrowser() {
return gulp.src('./dist/es2015/firebase.js')
.pipe(webpackStream({
plugins: [
new webpack.DefinePlugin({
TARGET_ENVIRONMENT: JSON.stringify('browser')
})
]
}, webpack))
.pipe(sourcemaps.init({ loadMaps: true }))
.pipe(through.obj(function(file, enc, cb) {
// Dont pipe through any source map files as it will be handled
// by gulp-sourcemaps
var isSourceMap = /\.map$/.test(file.path);
if (!isSourceMap) this.push(file);
cb();
}))
.pipe(sourcemaps.write('.'))
.pipe(gulp.dest(`${config.paths.outDir}/browser`));
}

function buildBrowserFirebaseJs() {
return gulp.src('./dist/browser/*.js')
.pipe(sourcemaps.init({ loadMaps: true }))
Expand All @@ -222,32 +202,18 @@ function buildBrowserFirebaseJs() {
}

function buildAltEnvFirebaseJs() {
const envs = [
'browser',
'node',
'react-native'
];

const streams = envs.map(env => {
const babelConfig = Object.assign({}, config.babel, {
plugins: [
['inline-replace-variables', {
'TARGET_ENVIRONMENT': env
}],
...config.babel.plugins
]
});
return gulp.src('./dist/es2015/firebase.js')
.pipe(sourcemaps.init({ loadMaps: true }))
.pipe(babel(babelConfig))
.pipe(rename({
suffix: `-${env}`
}))
.pipe(sourcemaps.write('.'))
.pipe(gulp.dest(`${config.paths.outDir}/cjs`));
const babelConfig = Object.assign({}, config.babel, {
plugins: config.babel.plugins
});

return merge(streams);
return gulp.src([
'./dist/es2015/firebase-browser.js',
'./dist/es2015/firebase-node.js',
'./dist/es2015/firebase-react-native.js',
])
.pipe(sourcemaps.init({ loadMaps: true }))
.pipe(babel(babelConfig))
.pipe(sourcemaps.write('.'))
.pipe(gulp.dest(`${config.paths.outDir}/cjs`));
}

function copyPackageContents() {
Expand Down
65 changes: 65 additions & 0 deletions src/database.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
/**
* Copyright 2017 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

import firebase from './app';
import { FirebaseApp, FirebaseNamespace } from "./app/firebase_app";
import { Database } from "./database/api/Database";
import { Query } from "./database/api/Query";
import { Reference } from "./database/api/Reference";
import { enableLogging } from "./database/core/util/util";
import { RepoManager } from "./database/core/RepoManager";
import * as INTERNAL from './database/api/internal';
import * as TEST_ACCESS from './database/api/test_access';
import { isNodeSdk } from "./utils/environment";

export function registerDatabase(instance) {
// Register the Database Service with the 'firebase' namespace.
const namespace = instance.INTERNAL.registerService(
'database',
app => RepoManager.getInstance().databaseFromApp(app),
// firebase.database namespace properties
{
Reference,
Query,
Database,
enableLogging,
INTERNAL,
ServerValue: Database.ServerValue,
TEST_ACCESS
}
);

if (isNodeSdk()) {
module.exports = namespace;
}
}

/**
* Extensions to the FirebaseApp and FirebaseNamespaces interfaces
*/
declare module './app/firebase_app' {
interface FirebaseApp {
database?(): Database
}
}

declare module './app/firebase_app' {
interface FirebaseNamespace {
database?(app: FirebaseApp): Database
}
}

registerDatabase(firebase);
168 changes: 168 additions & 0 deletions src/database/api/DataSnapshot.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,168 @@
import { validateArgCount, validateCallback } from '../../utils/validation';
import { validatePathString } from '../core/util/validation';
import { Path } from '../core/util/Path';
import { PRIORITY_INDEX } from '../core/snap/indexes/PriorityIndex';
import { Node } from '../core/snap/Node';
import { Reference } from './Reference';
import { Index } from '../core/snap/indexes/Index';
import { ChildrenNode } from '../core/snap/ChildrenNode';

/**
* Class representing a firebase data snapshot. It wraps a SnapshotNode and
* surfaces the public methods (val, forEach, etc.) we want to expose.
*/
export class DataSnapshot {
/**
* @param {!Node} node_ A SnapshotNode to wrap.
* @param {!Reference} ref_ The ref of the location this snapshot came from.
* @param {!Index} index_ The iteration order for this snapshot
*/
constructor(private readonly node_: Node,
private readonly ref_: Reference,
private readonly index_: Index) {
}

/**
* Retrieves the snapshot contents as JSON. Returns null if the snapshot is
* empty.
*
* @return {*} JSON representation of the DataSnapshot contents, or null if empty.
*/
val(): any {
validateArgCount('DataSnapshot.val', 0, 0, arguments.length);
return this.node_.val();
}

/**
* Returns the snapshot contents as JSON, including priorities of node. Suitable for exporting
* the entire node contents.
* @return {*} JSON representation of the DataSnapshot contents, or null if empty.
*/
exportVal(): any {
validateArgCount('DataSnapshot.exportVal', 0, 0, arguments.length);
return this.node_.val(true);
}

// Do not create public documentation. This is intended to make JSON serialization work but is otherwise unnecessary
// for end-users
toJSON(): any {
// Optional spacer argument is unnecessary because we're depending on recursion rather than stringifying the content
validateArgCount('DataSnapshot.toJSON', 0, 1, arguments.length);
return this.exportVal();
}

/**
* Returns whether the snapshot contains a non-null value.
*
* @return {boolean} Whether the snapshot contains a non-null value, or is empty.
*/
exists(): boolean {
validateArgCount('DataSnapshot.exists', 0, 0, arguments.length);
return !this.node_.isEmpty();
}

/**
* Returns a DataSnapshot of the specified child node's contents.
*
* @param {!string} childPathString Path to a child.
* @return {!DataSnapshot} DataSnapshot for child node.
*/
child(childPathString: string): DataSnapshot {
validateArgCount('DataSnapshot.child', 0, 1, arguments.length);
// Ensure the childPath is a string (can be a number)
childPathString = String(childPathString);
validatePathString('DataSnapshot.child', 1, childPathString, false);

const childPath = new Path(childPathString);
const childRef = this.ref_.child(childPath);
return new DataSnapshot(this.node_.getChild(childPath), childRef, PRIORITY_INDEX);
}

/**
* Returns whether the snapshot contains a child at the specified path.
*
* @param {!string} childPathString Path to a child.
* @return {boolean} Whether the child exists.
*/
hasChild(childPathString: string): boolean {
validateArgCount('DataSnapshot.hasChild', 1, 1, arguments.length);
validatePathString('DataSnapshot.hasChild', 1, childPathString, false);

const childPath = new Path(childPathString);
return !this.node_.getChild(childPath).isEmpty();
}

/**
* Returns the priority of the object, or null if no priority was set.
*
* @return {string|number|null} The priority.
*/
getPriority(): string | number | null {
validateArgCount('DataSnapshot.getPriority', 0, 0, arguments.length);

// typecast here because we never return deferred values or internal priorities (MAX_PRIORITY)
return /**@type {string|number|null} */ <string | number | null>(this.node_.getPriority().val());
}

/**
* Iterates through child nodes and calls the specified action for each one.
*
* @param {function(!DataSnapshot)} action Callback function to be called
* for each child.
* @return {boolean} True if forEach was canceled by action returning true for
* one of the child nodes.
*/
forEach(action: (d: DataSnapshot) => any): boolean {
validateArgCount('DataSnapshot.forEach', 1, 1, arguments.length);
validateCallback('DataSnapshot.forEach', 1, action, false);

if (this.node_.isLeafNode())
return false;

const childrenNode = /**@type {ChildrenNode} */ <ChildrenNode>(this.node_);
// Sanitize the return value to a boolean. ChildrenNode.forEachChild has a weird return type...
return !!childrenNode.forEachChild(this.index_, (key, node) => {
return action(new DataSnapshot(node, this.ref_.child(key), PRIORITY_INDEX));
});
}

/**
* Returns whether this DataSnapshot has children.
* @return {boolean} True if the DataSnapshot contains 1 or more child nodes.
*/
hasChildren(): boolean {
validateArgCount('DataSnapshot.hasChildren', 0, 0, arguments.length);

if (this.node_.isLeafNode())
return false;
else
return !this.node_.isEmpty();
}

get key() {
return this.ref_.getKey();
}

/**
* Returns the number of children for this DataSnapshot.
* @return {number} The number of children that this DataSnapshot contains.
*/
numChildren(): number {
validateArgCount('DataSnapshot.numChildren', 0, 0, arguments.length);

return this.node_.numChildren();
}

/**
* @return {Reference} The Firebase reference for the location this snapshot's data came from.
*/
getRef(): Reference {
validateArgCount('DataSnapshot.ref', 0, 0, arguments.length);

return this.ref_;
}

get ref() {
return this.getRef();
}
}
Loading

0 comments on commit bb80d31

Please sign in to comment.