Skip to content
This repository has been archived by the owner on Apr 4, 2023. It is now read-only.

Rework webApi queries #1111

Open
wants to merge 18 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
646b338
Add timestamp for webapi
KkevinLi Mar 1, 2019
d36d41c
[query] - Add webQueryInterface to implement database queries through…
KkevinLi Jan 16, 2019
9c193fc
[query] - Rework firebaseWebApi queries to allow chaining of filters.…
KkevinLi Jan 16, 2019
e8406dc
[query] - Rework firebaseWebApi queries to allow chaining of filters.…
KkevinLi Jan 16, 2019
b08f534
[query] - Rework database/index Query class to use the new query api
KkevinLi Jan 16, 2019
8a180c9
[readme] - Added documentation for webApi queries
KkevinLi Jan 16, 2019
b7014ae
[query] - Update web query class to have all filter operations to be …
KkevinLi Mar 5, 2019
f7d813d
Type query.once() to return a promise with datasnapshot
KkevinLi Mar 5, 2019
ebf8ff4
[query][android] - Make webapi queries return a datasnapshot that fol…
KkevinLi Mar 5, 2019
4d6a394
[query][ios] - Make webapi queries return a datasnapshot that follows…
KkevinLi Mar 5, 2019
9d08715
[docs][database] - Update webapi query documentation
KkevinLi Mar 5, 2019
1409b25
[chore] - Remove short references as they are deprecated in later ver…
KkevinLi Mar 5, 2019
a5fcb63
[chore] - White space
KkevinLi Mar 5, 2019
c02d7ae
[chore] - Fix typing errors
KkevinLi Mar 5, 2019
6244c7a
[query] - Update query.on() to follow web api and returns a Function …
KkevinLi Mar 7, 2019
7ffc302
[query] - Accept errorCallback for query.on()
KkevinLi Mar 7, 2019
414197f
[docs][database] - Update webapi queries to show error callbacks
KkevinLi Mar 7, 2019
1e7f884
[query] - Implement query.off correctly to allow removal of callbacks
KkevinLi Oct 3, 2019
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions demo/app/vendor-platform.android.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
require("application");
require("tns-core-modules/application");
if (!global["__snapshot"]) {
// In case snapshot generation is enabled these modules will get into the bundle
// but will not be required/evaluated.
// The snapshot webpack plugin will add them to the tns-java-classes.js bundle file.
// This way, they will be evaluated on app start as early as possible.
require("ui/frame");
require("ui/frame/activity");
require("tns-core-modules/ui/frame");
require("tns-core-modules/ui/frame/activity");
}
4 changes: 2 additions & 2 deletions demo/app/vendor.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
// Snapshot the ~/app.css and the theme
const application = require("application");
require("ui/styling/style-scope");
const application = require("tns-core-modules/application");
require("tns-core-modules/ui/styling/style-scope");
const appCssContext = require.context("~/", false, /^\.\/app\.(css|scss|less|sass)$/);
global.registerWebpackModules(appCssContext);
application.loadAppCss();
Expand Down
92 changes: 82 additions & 10 deletions docs/DATABASE.md
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,9 @@ Firebase supports querying data and this plugin does too, since v2.0.0.

Let's say we have the structure as defined at `setValue`, then use this query to retrieve the companies in country 'Bulgaria':

<details>
<summary>Native API</summary>

```js
var onQueryEvent = function(result) {
// note that the query returns 1 match at a time
Expand Down Expand Up @@ -216,6 +219,67 @@ Let's say we have the structure as defined at `setValue`, then use this query to
```

For supported values of the orderBy/range/ranges/limit's `type` properties, take a look at the [`firebase-common.d.ts`](firebase-common.d.ts) TypeScript definitions in this repo.
</details>
<details>
<summary>Web API</summary>

Alternatively you can use the web api to query data. See [docs](https://firebase.google.com/docs/reference/js/firebase.database.Query) for more examples and the full api

Some key notes:

The DataSnapshot returned is vastly different from the native api's snapshot! Please follow the web api docs to see what
you can do with the datasnapshot returned. Note that Datasnapshot.ref() is yet implemented.

`Query.on()` does not accept a cancelCallbackOrContext. Similar to the native api, check if result.error is true before continuing.

`once("eventType")` behaves differently on Android and iOS. On Android once only works with an eventType of `value` whereas
iOS will work with all the eventTypes like `child_added, child_removed` etc.

`off("eventType")` will remove all listeners for "eventType" at the given path. So you do not need to call `off()`
the same number of times you call `on()`. Listeners for all eventTypes will be removed if no eventType is provided.

Filters (`equalTo, startAt, endAt, LimitBy`, etc) should be used with a sort. If not, you may not get the result expected.
If you apply equalTo without an orderBy what are you checking for (key, value, priority)?

When using `equalTo, startAt or endAt` chained with `orderByKey()`, you MUST make sure they are all strings. Otherwise expect
an exception to be thrown.

DO NOT try to apply more than one orderBy to the same query as this will crash the application (follows the api)
```typescript
const bad = firebaseWebApi.database().ref(path).orderByKey();
bad.orderByValue(); // <------ will throw here!

// However you could do the following:
firebaseWebApi.database().ref("/companies").orderByKey()
.equalTo("Google")
.on("value", onQueryEvent);

firebaseWebApi.database().ref("/companies").orderByValue()
.startAt(1999)
.on("child_added", onQueryEvent);

firebaseWebApi.database().ref("/companies").off("value");

// You can also do the following
firebase.webQuery("/companies").orderByKey().on("value", onQueryEvent);

const onQueryEvent = (result: any) {
if (!result.error) {
console.log("Exists: " + result.exists());
console.log("Key: " + result.key);
console.log("Value: " + JSON.stringify(result.val()));
result.forEach(
snapshot => {
// Do something forEach children. Note that this goes one level deep
console.log(snapshot.toJSON());
}
);
}
};

```
Since the webapi queries follow the Google Documentation you can look at their examples for more reference.
</details>

### update
Changes the values of the keys specified in the dictionary without overwriting other keys at this location.
Expand Down Expand Up @@ -307,17 +371,25 @@ The link is for the iOS SDK, but it's the same for Android.
<summary>Web API</summary>

```js
const onValueEvent = result => {
if (result.error) {
console.log("Listener error: " + result.error);
} else {
console.log("Key: " + result.key);
console.log("key exists? " + result.exists());
console.log("Value: " + JSON.stringify(result.val()));
}
};
public doWebAddValueEventListenerForCompanies(): void {
const path = "/companies";
const onValueEvent = (result: firebase.DataSnapshot ) => {
// NOTE: we no longer check for result.error as it doesn't exist. Pass in an onError callback to handle errors!
console.log("value : " + result.forEach(datasnapshot => {
console.log(datasnapshot.key + " " + JSON.stringify(datasnapshot.val()));
}));
console.log("key exists? " + result.exists());
this.set("path", path);
this.set("key", result.key);
this.set("value", JSON.stringify(result.val()));
};

const onErrorEvent = (err: Error ) => {
console.log("Encountered an error: " + err);
};
firebaseWebApi.database().ref("/companies").on("value", onValueEvent, onErrorEvent /* Totally Optional */);
}

firebaseWebApi.database().ref("/companies").on("value", onValueEvent);
```
</details>

Expand Down
2 changes: 1 addition & 1 deletion src/app/auth/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { FirebaseEmailLinkActionCodeSettings, LoginType, User } from "../../fire
export module auth {
export class Auth {
private authStateChangedHandler;
public currentUser: User;
public currentUser: User | undefined;
public languageCode: string | null;

public onAuthStateChanged(handler: (user: User) => void): void {
Expand Down
217 changes: 89 additions & 128 deletions src/app/database/index.ts
Original file line number Diff line number Diff line change
@@ -1,154 +1,116 @@
import * as firebase from "../../firebase";
import { AddEventListenerResult, FBData } from "../../firebase";
import { nextPushId } from "./util/NextPushId";

export module database {
export interface DataSnapshot {
// child(path: string): DataSnapshot;
exists(): boolean;
// exportVal(): any;
// forEach(action: (a: DataSnapshot) => boolean): boolean;
// getPriority(): string | number | null;
// hasChild(path: string): boolean;
// hasChildren(): boolean;
key: string | null;
// numChildren(): number;
// ref: Reference;
// toJSON(): Object | null;
val(): any;
}

export class Query {
private static registeredListeners: Map<string, Array<any>> = new Map();
private static registeredCallbacks: Map<string, Array<(a: DataSnapshot | null, b?: string) => any>> = new Map();
export namespace database {
export type DataSnapshot = firebase.DataSnapshot;

export class Query implements firebase.Query {
protected path: string;

private queryObject: firebase.Query;
constructor(path: string) {
this.path = path;
this.queryObject = firebase.webQuery(this.path);
}

public on(eventType /* TODO use */: string, callback: (a: DataSnapshot | null, b?: string) => any, cancelCallbackOrContext?: Object | null, context?: Object | null): (a: DataSnapshot | null, b?: string) => any {
const onValueEvent = result => {
if (result.error) {
callback(result);
} else {
callback({
key: result.key,
val: () => result.value,
exists: () => !!result.value
});
}
};

firebase.addValueEventListener(onValueEvent, this.path).then(
(result: AddEventListenerResult) => {
if (!Query.registeredListeners.has(this.path)) {
Query.registeredListeners.set(this.path, []);
}
Query.registeredListeners.set(this.path, Query.registeredListeners.get(this.path).concat(result.listeners));
},
error => {
console.log("firebase.database().on error: " + error);
}
);
/**
* Listens for data changes at a particular location
* @param eventType One of the following strings: "value", "child_added", "child_changed", "child_removed", or "child_moved."
* @param callback A callback that fires when the specified event occurs. The callback will be passed a DataSnapshot.
* @param cancelCallbackOrContext A callback that fires when an error occurs. The callback will be passed an error object.
* @returns The provided callback function is returned unmodified.
*/
public on(eventType: string, callback: (a: DataSnapshot | null, b?: string) => any,
cancelCallbackOrContext?: (a: Error | null) => any, context?: Object | null): (a: DataSnapshot | null, b?: string) => Function {

// remember the callbacks as we may need them for 'query' events
if (!Query.registeredCallbacks.has(this.path)) {
Query.registeredCallbacks.set(this.path, []);
}
Query.registeredCallbacks.get(this.path).push(callback);
this.queryObject.on(eventType, callback, cancelCallbackOrContext);

return null;
return callback; // According to firebase doc we just return the callback given
}

public off(eventType? /* TODO use */: string, callback?: (a: DataSnapshot, b?: string | null) => any, context?: Object | null): any {
if (Query.registeredListeners.has(this.path)) {
firebase.removeEventListeners(Query.registeredListeners.get(this.path), this.path).then(
result => Query.registeredListeners.delete(this.path),
error => console.log("firebase.database().off error: " + error)
);
}
Query.registeredCallbacks.delete(this.path);
return null;
/**
* Remove all callbacks for given eventType. If not eventType is given this
* detaches all callbacks previously attached with on().
*/
public off(eventType?: string, callback?: (a: DataSnapshot, b?: string | null) => any, context?: Object | null): void {
// TODO: use callback rather than remove ALL listeners for a given eventType
this.queryObject.off(eventType, callback);
}

public once(eventType: string, successCallback?: (a: DataSnapshot, b?: string) => any, failureCallbackOrContext?: Object | null, context?: Object | null): Promise<DataSnapshot> {
return new Promise((resolve, reject) => {
firebase.getValue(this.path).then(result => {
resolve({
key: result.key,
val: () => result.value,
exists: () => !!result.value
});
});
});
/**
* Listens for exactly one event of the specified event type, and then stops listening.
* @param eventType One of the following strings: "value", "child_added", "child_changed", "child_removed", or "child_moved."
*/
public once(eventType: string, successCallback?: (a: DataSnapshot, b?: string) => any,
failureCallbackOrContext?: Object | null, context?: Object | null): Promise<DataSnapshot> {
return this.queryObject.once(eventType);
}

private getOnValueEventHandler(): (data: FBData) => void {
return result => {
const callbacks = Query.registeredCallbacks.get(this.path);
callbacks && callbacks.map(callback => {
callback({
key: result.key,
val: () => result.value,
exists: () => !!result.value
});
});
};
/**
* Generates a new Query object ordered by the specified child key. Queries can only order
* by one key at a time. Calling orderByChild() multiple times on the same query is an error.
* @param child child key to order the results by
*/
public orderByChild(child: string): firebase.Query {
return this.queryObject.orderByChild(child);
}
/**
* Generates a new Query object ordered by key.
* Sorts the results of a query by their (ascending) key values.
*/
public orderByKey(): firebase.Query {
return this.queryObject.orderByKey();
}

public orderByChild(child: string): Query {
firebase.query(
this.getOnValueEventHandler(),
this.path,
{
orderBy: {
type: firebase.QueryOrderByType.CHILD,
value: child
}
}
);
return this;
/**
* Generates a new Query object ordered by priority
*/
public orderByPriority(): firebase.Query {
return this.queryObject.orderByPriority();
}

public orderByKey(): Query {
firebase.query(
this.getOnValueEventHandler(),
this.path,
{
orderBy: {
type: firebase.QueryOrderByType.KEY
}
}
);
return this;
/**
* Generates a new Query object ordered by value.If the children of a query are all scalar values
* (string, number, or boolean), you can order the results by their (ascending) values.
*/
public orderByValue(): firebase.Query {
return this.queryObject.orderByValue();
}

public orderByPriority(): Query {
firebase.query(
this.getOnValueEventHandler(),
this.path,
{
orderBy: {
type: firebase.QueryOrderByType.PRIORITY
}
}
);
return this;
/**
* Creates a Query with the specified starting point. The value to start at should match the type
* passed to orderBy(). If using orderByKey(), the value must be a string
*/
public startAt(value: number | string | boolean): firebase.Query {
return this.queryObject.startAt(value);
}

public orderByValue(): Query {
firebase.query(
this.getOnValueEventHandler(),
this.path,
{
orderBy: {
type: firebase.QueryOrderByType.VALUE
}
}
);
return this;
/**
* Creates a Query with the specified ending point. The value to start at should match the type
* passed to orderBy(). If using orderByKey(), the value must be a string.
*/
public endAt(value: any, key?: string): firebase.Query {
return this.queryObject.endAt(value, key);
}

/**
* Generate a new Query limited to the first specific number of children.
*/
public limitToFirst(value: number): firebase.Query {
return this.queryObject.limitToFirst(value);
}

/**
* Generate a new Query limited to the last specific number of children.
*/
public limitToLast(value: number): firebase.Query {
return this.queryObject.limitToLast(value);
}

/**
* Creates a Query that includes children that match the specified value.
*/
public equalTo(value: any, key?: string): firebase.Query {
return this.queryObject.equalTo(value, key);
}
}

Expand Down Expand Up @@ -258,8 +220,7 @@ export module database {
}
}

export interface ThenableReference extends Reference /*, PromiseLike<any> */
{
export interface ThenableReference extends Reference /*, PromiseLike<any> */ {
}

export class Database {
Expand Down
Loading