443 lines
18 KiB
JavaScript
443 lines
18 KiB
JavaScript
import * as i0 from '@angular/core';
|
|
import { InjectionToken, PLATFORM_ID, Injectable, Inject, Optional, NgModule } from '@angular/core';
|
|
import { asyncScheduler, Observable, of, merge } from 'rxjs';
|
|
import { map, share, switchMap, scan, distinctUntilChanged, withLatestFrom, skipWhile } from 'rxjs/operators';
|
|
import * as i1 from '@angular/fire';
|
|
import { keepUnstableUntilFirst, VERSION } from '@angular/fire';
|
|
import { ɵfirebaseAppFactory, ɵcacheInstance, FIREBASE_OPTIONS, FIREBASE_APP_NAME } from '@angular/fire/compat';
|
|
import 'firebase/compat/auth';
|
|
import 'firebase/compat/database';
|
|
import * as i2 from '@angular/fire/compat/auth';
|
|
import { ɵauthFactory, USE_EMULATOR as USE_EMULATOR$1, SETTINGS, TENANT_ID, LANGUAGE_CODE, USE_DEVICE_LANGUAGE, PERSISTENCE } from '@angular/fire/compat/auth';
|
|
import * as i3 from '@angular/fire/app-check';
|
|
import firebase from 'firebase/compat/app';
|
|
|
|
function isString(value) {
|
|
return typeof value === 'string';
|
|
}
|
|
function isFirebaseDataSnapshot(value) {
|
|
return typeof value.exportVal === 'function';
|
|
}
|
|
function isNil(obj) {
|
|
return obj === undefined || obj === null;
|
|
}
|
|
function isFirebaseRef(value) {
|
|
return typeof value.set === 'function';
|
|
}
|
|
/**
|
|
* Returns a database reference given a Firebase App and an
|
|
* absolute or relative path.
|
|
* @param database - Firebase Database
|
|
* @param pathRef - Database path, relative or absolute
|
|
*/
|
|
function getRef(database, pathRef) {
|
|
// if a db ref was passed in, just return it
|
|
return isFirebaseRef(pathRef) ? pathRef
|
|
: database.ref(pathRef);
|
|
}
|
|
function checkOperationCases(item, cases) {
|
|
if (isString(item)) {
|
|
return cases.stringCase();
|
|
}
|
|
else if (isFirebaseRef(item)) {
|
|
return cases.firebaseCase();
|
|
}
|
|
else if (isFirebaseDataSnapshot(item)) {
|
|
return cases.snapshotCase();
|
|
}
|
|
throw new Error(`Expects a string, snapshot, or reference. Got: ${typeof item}`);
|
|
}
|
|
|
|
/**
|
|
* Create an observable from a Database Reference or Database Query.
|
|
* @param ref Database Reference
|
|
* @param event Listen event type ('value', 'added', 'changed', 'removed', 'moved')
|
|
* @param listenType 'on' or 'once'
|
|
* @param scheduler - Rxjs scheduler
|
|
*/
|
|
function fromRef(ref, event, listenType = 'on', scheduler = asyncScheduler) {
|
|
return new Observable(subscriber => {
|
|
let fn = null;
|
|
fn = ref[listenType](event, (snapshot, prevKey) => {
|
|
scheduler.schedule(() => {
|
|
subscriber.next({ snapshot, prevKey });
|
|
});
|
|
if (listenType === 'once') {
|
|
scheduler.schedule(() => subscriber.complete());
|
|
}
|
|
}, err => {
|
|
scheduler.schedule(() => subscriber.error(err));
|
|
});
|
|
if (listenType === 'on') {
|
|
return {
|
|
unsubscribe() {
|
|
if (fn != null) {
|
|
ref.off(event, fn);
|
|
}
|
|
}
|
|
};
|
|
}
|
|
else {
|
|
return {
|
|
unsubscribe() {
|
|
}
|
|
};
|
|
}
|
|
}).pipe(map(payload => {
|
|
const { snapshot, prevKey } = payload;
|
|
let key = null;
|
|
if (snapshot.exists()) {
|
|
key = snapshot.key;
|
|
}
|
|
return { type: event, payload: snapshot, prevKey, key };
|
|
}), share());
|
|
}
|
|
|
|
function listChanges(ref, events, scheduler) {
|
|
return fromRef(ref, 'value', 'once', scheduler).pipe(switchMap(snapshotAction => {
|
|
const childEvent$ = [of(snapshotAction)];
|
|
events.forEach(event => childEvent$.push(fromRef(ref, event, 'on', scheduler)));
|
|
return merge(...childEvent$).pipe(scan(buildView, []));
|
|
}), distinctUntilChanged());
|
|
}
|
|
function positionFor(changes, key) {
|
|
const len = changes.length;
|
|
for (let i = 0; i < len; i++) {
|
|
if (changes[i].payload.key === key) {
|
|
return i;
|
|
}
|
|
}
|
|
return -1;
|
|
}
|
|
function positionAfter(changes, prevKey) {
|
|
if (isNil(prevKey)) {
|
|
return 0;
|
|
}
|
|
else {
|
|
const i = positionFor(changes, prevKey);
|
|
if (i === -1) {
|
|
return changes.length;
|
|
}
|
|
else {
|
|
return i + 1;
|
|
}
|
|
}
|
|
}
|
|
function buildView(current, action) {
|
|
const { payload, prevKey, key } = action;
|
|
const currentKeyPosition = positionFor(current, key);
|
|
const afterPreviousKeyPosition = positionAfter(current, prevKey);
|
|
switch (action.type) {
|
|
case 'value':
|
|
if (action.payload && action.payload.exists()) {
|
|
let prevKey = null;
|
|
action.payload.forEach(payload => {
|
|
const action = { payload, type: 'value', prevKey, key: payload.key };
|
|
prevKey = payload.key;
|
|
current = [...current, action];
|
|
return false;
|
|
});
|
|
}
|
|
return current;
|
|
case 'child_added':
|
|
if (currentKeyPosition > -1) {
|
|
// check that the previouskey is what we expect, else reorder
|
|
const previous = current[currentKeyPosition - 1];
|
|
if ((previous && previous.key || null) !== prevKey) {
|
|
current = current.filter(x => x.payload.key !== payload.key);
|
|
current.splice(afterPreviousKeyPosition, 0, action);
|
|
}
|
|
}
|
|
else if (prevKey == null) {
|
|
return [action, ...current];
|
|
}
|
|
else {
|
|
current = current.slice();
|
|
current.splice(afterPreviousKeyPosition, 0, action);
|
|
}
|
|
return current;
|
|
case 'child_removed':
|
|
return current.filter(x => x.payload.key !== payload.key);
|
|
case 'child_changed':
|
|
return current.map(x => x.payload.key === key ? action : x);
|
|
case 'child_moved':
|
|
if (currentKeyPosition > -1) {
|
|
const data = current.splice(currentKeyPosition, 1)[0];
|
|
current = current.slice();
|
|
current.splice(afterPreviousKeyPosition, 0, data);
|
|
return current;
|
|
}
|
|
return current;
|
|
// default will also remove null results
|
|
default:
|
|
return current;
|
|
}
|
|
}
|
|
|
|
function validateEventsArray(events) {
|
|
if (isNil(events) || events.length === 0) {
|
|
events = ['child_added', 'child_removed', 'child_changed', 'child_moved'];
|
|
}
|
|
return events;
|
|
}
|
|
|
|
function snapshotChanges(query, events, scheduler) {
|
|
events = validateEventsArray(events);
|
|
return listChanges(query, events, scheduler);
|
|
}
|
|
|
|
function stateChanges(query, events, scheduler) {
|
|
events = validateEventsArray(events);
|
|
const childEvent$ = events.map(event => fromRef(query, event, 'on', scheduler));
|
|
return merge(...childEvent$);
|
|
}
|
|
|
|
function auditTrail(query, events, scheduler) {
|
|
const auditTrail$ = stateChanges(query, events)
|
|
.pipe(scan((current, action) => [...current, action], []));
|
|
return waitForLoaded(query, auditTrail$, scheduler);
|
|
}
|
|
function loadedData(query, scheduler) {
|
|
// Create an observable of loaded values to retrieve the
|
|
// known dataset. This will allow us to know what key to
|
|
// emit the "whole" array at when listening for child events.
|
|
return fromRef(query, 'value', 'on', scheduler)
|
|
.pipe(map(data => {
|
|
// Store the last key in the data set
|
|
let lastKeyToLoad;
|
|
// Loop through loaded dataset to find the last key
|
|
data.payload.forEach(child => {
|
|
lastKeyToLoad = child.key;
|
|
return false;
|
|
});
|
|
// return data set and the current last key loaded
|
|
return { data, lastKeyToLoad };
|
|
}));
|
|
}
|
|
function waitForLoaded(query, action$, scheduler) {
|
|
const loaded$ = loadedData(query, scheduler);
|
|
return loaded$
|
|
.pipe(withLatestFrom(action$),
|
|
// Get the latest values from the "loaded" and "child" datasets
|
|
// We can use both datasets to form an array of the latest values.
|
|
map(([loaded, actions]) => {
|
|
// Store the last key in the data set
|
|
const lastKeyToLoad = loaded.lastKeyToLoad;
|
|
// Store all child keys loaded at this point
|
|
const loadedKeys = actions.map(snap => snap.key);
|
|
return { actions, lastKeyToLoad, loadedKeys };
|
|
}),
|
|
// This is the magical part, only emit when the last load key
|
|
// in the dataset has been loaded by a child event. At this point
|
|
// we can assume the dataset is "whole".
|
|
skipWhile(meta => meta.loadedKeys.indexOf(meta.lastKeyToLoad) === -1),
|
|
// Pluck off the meta data because the user only cares
|
|
// to iterate through the snapshots
|
|
map(meta => meta.actions));
|
|
}
|
|
|
|
function createDataOperationMethod(ref, operation) {
|
|
return function dataOperation(item, value) {
|
|
return checkOperationCases(item, {
|
|
stringCase: () => ref.child(item)[operation](value),
|
|
firebaseCase: () => item[operation](value),
|
|
snapshotCase: () => item.ref[operation](value)
|
|
});
|
|
};
|
|
}
|
|
|
|
// TODO(davideast): Find out why TS thinks this returns firebase.Primise
|
|
// instead of Promise.
|
|
function createRemoveMethod(ref) {
|
|
return function remove(item) {
|
|
if (!item) {
|
|
return ref.remove();
|
|
}
|
|
return checkOperationCases(item, {
|
|
stringCase: () => ref.child(item).remove(),
|
|
firebaseCase: () => item.remove(),
|
|
snapshotCase: () => item.ref.remove()
|
|
});
|
|
};
|
|
}
|
|
|
|
function createListReference(query, afDatabase) {
|
|
const outsideAngularScheduler = afDatabase.schedulers.outsideAngular;
|
|
const refInZone = afDatabase.schedulers.ngZone.run(() => query.ref);
|
|
return {
|
|
query,
|
|
update: createDataOperationMethod(refInZone, 'update'),
|
|
set: createDataOperationMethod(refInZone, 'set'),
|
|
push: (data) => refInZone.push(data),
|
|
remove: createRemoveMethod(refInZone),
|
|
snapshotChanges(events) {
|
|
return snapshotChanges(query, events, outsideAngularScheduler).pipe(keepUnstableUntilFirst);
|
|
},
|
|
stateChanges(events) {
|
|
return stateChanges(query, events, outsideAngularScheduler).pipe(keepUnstableUntilFirst);
|
|
},
|
|
auditTrail(events) {
|
|
return auditTrail(query, events, outsideAngularScheduler).pipe(keepUnstableUntilFirst);
|
|
},
|
|
valueChanges(events, options) {
|
|
const snapshotChanges$ = snapshotChanges(query, events, outsideAngularScheduler);
|
|
return snapshotChanges$.pipe(map(actions => actions.map(a => {
|
|
if (options && options.idField) {
|
|
return Object.assign(Object.assign({}, a.payload.val()), {
|
|
[options.idField]: a.key
|
|
});
|
|
}
|
|
else {
|
|
return a.payload.val();
|
|
}
|
|
})), keepUnstableUntilFirst);
|
|
}
|
|
};
|
|
}
|
|
|
|
function createObjectSnapshotChanges(query, scheduler) {
|
|
return function snapshotChanges() {
|
|
return fromRef(query, 'value', 'on', scheduler);
|
|
};
|
|
}
|
|
|
|
function createObjectReference(query, afDatabase) {
|
|
return {
|
|
query,
|
|
snapshotChanges() {
|
|
return createObjectSnapshotChanges(query, afDatabase.schedulers.outsideAngular)().pipe(keepUnstableUntilFirst);
|
|
},
|
|
update(data) { return query.ref.update(data); },
|
|
set(data) { return query.ref.set(data); },
|
|
remove() { return query.ref.remove(); },
|
|
valueChanges() {
|
|
const snapshotChanges$ = createObjectSnapshotChanges(query, afDatabase.schedulers.outsideAngular)();
|
|
return snapshotChanges$.pipe(keepUnstableUntilFirst, map(action => action.payload.exists() ? action.payload.val() : null));
|
|
},
|
|
};
|
|
}
|
|
|
|
const URL = new InjectionToken('angularfire2.realtimeDatabaseURL');
|
|
const USE_EMULATOR = new InjectionToken('angularfire2.database.use-emulator');
|
|
class AngularFireDatabase {
|
|
constructor(options, name, databaseURL,
|
|
// tslint:disable-next-line:ban-types
|
|
platformId, zone, schedulers, _useEmulator, // tuple isn't working here
|
|
auth, useAuthEmulator, authSettings, // can't use firebase.auth.AuthSettings here
|
|
tenantId, languageCode, useDeviceLanguage, persistence, _appCheckInstances) {
|
|
this.schedulers = schedulers;
|
|
const useEmulator = _useEmulator;
|
|
const app = ɵfirebaseAppFactory(options, zone, name);
|
|
if (auth) {
|
|
ɵauthFactory(app, zone, useAuthEmulator, tenantId, languageCode, useDeviceLanguage, authSettings, persistence);
|
|
}
|
|
this.database = ɵcacheInstance(`${app.name}.database.${databaseURL}`, 'AngularFireDatabase', app.name, () => {
|
|
const database = zone.runOutsideAngular(() => app.database(databaseURL || undefined));
|
|
if (useEmulator) {
|
|
database.useEmulator(...useEmulator);
|
|
}
|
|
return database;
|
|
}, [useEmulator]);
|
|
}
|
|
list(pathOrRef, queryFn) {
|
|
const ref = this.schedulers.ngZone.runOutsideAngular(() => getRef(this.database, pathOrRef));
|
|
let query = ref;
|
|
if (queryFn) {
|
|
query = queryFn(ref);
|
|
}
|
|
return createListReference(query, this);
|
|
}
|
|
object(pathOrRef) {
|
|
const ref = this.schedulers.ngZone.runOutsideAngular(() => getRef(this.database, pathOrRef));
|
|
return createObjectReference(ref, this);
|
|
}
|
|
createPushId() {
|
|
const ref = this.schedulers.ngZone.runOutsideAngular(() => this.database.ref());
|
|
return ref.push().key;
|
|
}
|
|
}
|
|
AngularFireDatabase.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "12.1.3", ngImport: i0, type: AngularFireDatabase, deps: [{ token: FIREBASE_OPTIONS }, { token: FIREBASE_APP_NAME, optional: true }, { token: URL, optional: true }, { token: PLATFORM_ID }, { token: i0.NgZone }, { token: i1.ɵAngularFireSchedulers }, { token: USE_EMULATOR, optional: true }, { token: i2.AngularFireAuth, optional: true }, { token: USE_EMULATOR$1, optional: true }, { token: SETTINGS, optional: true }, { token: TENANT_ID, optional: true }, { token: LANGUAGE_CODE, optional: true }, { token: USE_DEVICE_LANGUAGE, optional: true }, { token: PERSISTENCE, optional: true }, { token: i3.AppCheckInstances, optional: true }], target: i0.ɵɵFactoryTarget.Injectable });
|
|
AngularFireDatabase.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "12.1.3", ngImport: i0, type: AngularFireDatabase, providedIn: 'any' });
|
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "12.1.3", ngImport: i0, type: AngularFireDatabase, decorators: [{
|
|
type: Injectable,
|
|
args: [{
|
|
providedIn: 'any'
|
|
}]
|
|
}], ctorParameters: function () { return [{ type: undefined, decorators: [{
|
|
type: Inject,
|
|
args: [FIREBASE_OPTIONS]
|
|
}] }, { type: undefined, decorators: [{
|
|
type: Optional
|
|
}, {
|
|
type: Inject,
|
|
args: [FIREBASE_APP_NAME]
|
|
}] }, { type: undefined, decorators: [{
|
|
type: Optional
|
|
}, {
|
|
type: Inject,
|
|
args: [URL]
|
|
}] }, { type: Object, decorators: [{
|
|
type: Inject,
|
|
args: [PLATFORM_ID]
|
|
}] }, { type: i0.NgZone }, { type: i1.ɵAngularFireSchedulers }, { type: undefined, decorators: [{
|
|
type: Optional
|
|
}, {
|
|
type: Inject,
|
|
args: [USE_EMULATOR]
|
|
}] }, { type: i2.AngularFireAuth, decorators: [{
|
|
type: Optional
|
|
}] }, { type: undefined, decorators: [{
|
|
type: Optional
|
|
}, {
|
|
type: Inject,
|
|
args: [USE_EMULATOR$1]
|
|
}] }, { type: undefined, decorators: [{
|
|
type: Optional
|
|
}, {
|
|
type: Inject,
|
|
args: [SETTINGS]
|
|
}] }, { type: undefined, decorators: [{
|
|
type: Optional
|
|
}, {
|
|
type: Inject,
|
|
args: [TENANT_ID]
|
|
}] }, { type: undefined, decorators: [{
|
|
type: Optional
|
|
}, {
|
|
type: Inject,
|
|
args: [LANGUAGE_CODE]
|
|
}] }, { type: undefined, decorators: [{
|
|
type: Optional
|
|
}, {
|
|
type: Inject,
|
|
args: [USE_DEVICE_LANGUAGE]
|
|
}] }, { type: undefined, decorators: [{
|
|
type: Optional
|
|
}, {
|
|
type: Inject,
|
|
args: [PERSISTENCE]
|
|
}] }, { type: i3.AppCheckInstances, decorators: [{
|
|
type: Optional
|
|
}] }]; } });
|
|
|
|
class AngularFireDatabaseModule {
|
|
constructor() {
|
|
firebase.registerVersion('angularfire', VERSION.full, 'rtdb-compat');
|
|
}
|
|
}
|
|
AngularFireDatabaseModule.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "12.1.3", ngImport: i0, type: AngularFireDatabaseModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule });
|
|
AngularFireDatabaseModule.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "12.0.0", version: "12.1.3", ngImport: i0, type: AngularFireDatabaseModule });
|
|
AngularFireDatabaseModule.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "12.1.3", ngImport: i0, type: AngularFireDatabaseModule, providers: [AngularFireDatabase] });
|
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "12.1.3", ngImport: i0, type: AngularFireDatabaseModule, decorators: [{
|
|
type: NgModule,
|
|
args: [{
|
|
providers: [AngularFireDatabase]
|
|
}]
|
|
}], ctorParameters: function () { return []; } });
|
|
|
|
/**
|
|
* Generated bundle index. Do not edit.
|
|
*/
|
|
|
|
export { AngularFireDatabase, AngularFireDatabaseModule, URL, USE_EMULATOR, auditTrail, createListReference, fromRef, listChanges, snapshotChanges, stateChanges };
|
|
//# sourceMappingURL=angular-fire-compat-database.js.map
|