diff --git a/packages/app/lib/common/index.ts b/packages/app/lib/common/index.ts index 9c6753cb8b..3089e5dd02 100644 --- a/packages/app/lib/common/index.ts +++ b/packages/app/lib/common/index.ts @@ -310,6 +310,10 @@ const mapOfDeprecationReplacements: DeprecationMap = { statics: { ServerValue: 'ServerValue', }, + ServerValue: { + increment: 'increment()', + serverTimestamp: 'serverTimestamp()', + }, DatabaseReference: { child: 'child()', set: 'set()', @@ -595,6 +599,10 @@ function getNamespace(target: any): string | undefined { if (target.constructor.name === 'DatabaseReference') { return 'database'; } + // Check if target is ServerValue object (has increment method and TIMESTAMP property) + if (target.increment && target.TIMESTAMP && target.TIMESTAMP['.sv'] === 'timestamp') { + return 'database'; + } if (target.GeoPoint || target.CustomProvider) { // target is statics object. GeoPoint - Firestore, CustomProvider - AppCheck return 'firestore'; @@ -627,6 +635,11 @@ function getInstanceName(target: any): string { return 'default'; } + // Check if target is ServerValue object (has increment method and TIMESTAMP property) + if (target.increment && target.TIMESTAMP && target.TIMESTAMP['.sv'] === 'timestamp') { + return 'ServerValue'; + } + if (target.constructor.name === 'StorageReference') { // if path passed into ref(), it will pass in the arg as target.name return target.constructor.name; diff --git a/packages/database/__tests__/database.test.ts b/packages/database/__tests__/database.test.ts index 4d61a55903..5c1119fcce 100644 --- a/packages/database/__tests__/database.test.ts +++ b/packages/database/__tests__/database.test.ts @@ -357,6 +357,7 @@ describe('Database', function () { const db = getDatabase(); databaseV9Deprecation( () => connectDatabaseEmulator(db, 'localhost', 9000), + // @ts-expect-error Combines modular and namespace API () => db.useEmulator('localhost', 9000), 'useEmulator', ); @@ -366,6 +367,7 @@ describe('Database', function () { const db = getDatabase(); databaseV9Deprecation( () => goOffline(db), + // @ts-expect-error Combines modular and namespace API () => db.goOffline(), 'goOffline', ); @@ -375,6 +377,7 @@ describe('Database', function () { const db = getDatabase(); databaseV9Deprecation( () => goOnline(db), + // @ts-expect-error Combines modular and namespace API () => db.goOnline(), 'goOnline', ); @@ -384,6 +387,7 @@ describe('Database', function () { const db = getDatabase(); databaseV9Deprecation( () => ref(db, 'test'), + // @ts-expect-error Combines modular and namespace API () => db.ref('test'), 'ref', ); @@ -395,6 +399,7 @@ describe('Database', function () { (db as any)._customUrlOrRegion = 'https://test.firebaseio.com'; databaseV9Deprecation( () => refFromURL(db, 'https://test.firebaseio.com'), + // @ts-expect-error Combines modular and namespace API () => db.refFromURL('https://test.firebaseio.com'), 'refFromURL', ); @@ -404,6 +409,7 @@ describe('Database', function () { const db = getDatabase(); databaseV9Deprecation( () => setPersistenceEnabled(db, true), + // @ts-expect-error Combines modular and namespace API () => db.setPersistenceEnabled(true), 'setPersistenceEnabled', ); @@ -413,6 +419,7 @@ describe('Database', function () { const db = getDatabase(); databaseV9Deprecation( () => setLoggingEnabled(db, true), + // @ts-expect-error Combines modular and namespace API () => db.setLoggingEnabled(true), 'setLoggingEnabled', ); @@ -422,6 +429,7 @@ describe('Database', function () { const db = getDatabase(); databaseV9Deprecation( () => setPersistenceCacheSizeBytes(db, 10000000), + // @ts-expect-error Combines modular and namespace API () => db.setPersistenceCacheSizeBytes(10000000), 'setPersistenceCacheSizeBytes', ); @@ -431,6 +439,7 @@ describe('Database', function () { const db = getDatabase(); databaseV9Deprecation( () => getServerTime(db), + // @ts-expect-error Combines modular and namespace API () => db.getServerTime(), 'getServerTime', ); diff --git a/packages/database/e2e/issues.e2e.js b/packages/database/e2e/issues.e2e.js index 2142dbe751..280d5e9258 100644 --- a/packages/database/e2e/issues.e2e.js +++ b/packages/database/e2e/issues.e2e.js @@ -62,6 +62,10 @@ describe('database issues', function () { testRef._modifiers.toString().should.be.a.String(); testRef._modifiers.toArray()[0].name.should.equal('orderByChild'); + // Seed some data that matches the query to ensure callback fires + const seedRef = firebase.database().ref(`${TEST_PATH}/item1`); + await seedRef.set({ disabled: false, name: 'test' }); + testRef.on('value', snapshot => { callback(snapshot.val()); }); @@ -180,7 +184,8 @@ describe('database issues', function () { }); it('#2833 should not mutate modifiers ordering', async function () { - const { getDatabase, ref, child, query, equalTo, orderByChild, onValue } = databaseModular; + const { getDatabase, ref, child, query, equalTo, orderByChild, onValue, set } = + databaseModular; const callback = sinon.spy(); const testRef = query( @@ -192,6 +197,10 @@ describe('database issues', function () { testRef._modifiers.toString().should.be.a.String(); testRef._modifiers.toArray()[0].name.should.equal('orderByChild'); + // Seed some data that matches the query to ensure callback fires + const seedRef = child(ref(getDatabase()), `${TEST_PATH}/item1`); + await set(seedRef, { disabled: false, name: 'test' }); + const unsubscribe = onValue(testRef, snapshot => { callback(snapshot.val()); }); diff --git a/packages/database/lib/DatabaseDataSnapshot.js b/packages/database/lib/DatabaseDataSnapshot.ts similarity index 73% rename from packages/database/lib/DatabaseDataSnapshot.js rename to packages/database/lib/DatabaseDataSnapshot.ts index 2b8f5a7bd0..24230e9838 100644 --- a/packages/database/lib/DatabaseDataSnapshot.js +++ b/packages/database/lib/DatabaseDataSnapshot.ts @@ -26,15 +26,34 @@ import { import { deepGet } from '@react-native-firebase/app/dist/module/common/deeps'; import { MODULAR_DEPRECATION_ARG } from '@react-native-firebase/app/dist/module/common'; +import type DatabaseReference from './DatabaseReference'; +import type DatabaseQuery from './DatabaseQuery'; + +interface SnapshotData { + value: unknown; + key: string | null; + exists: boolean; + childKeys: string[]; + priority: string | number | null; + childPriorities?: { [key: string]: string | number | null }; +} + export default class DatabaseDataSnapshot { - constructor(reference, snapshot) { + _snapshot: SnapshotData; + _ref: DatabaseReference; + + constructor(reference: DatabaseReference | DatabaseQuery, snapshot: SnapshotData) { this._snapshot = snapshot; if (reference.key !== snapshot.key) { // reference is a query? - this._ref = reference.ref.child.call(reference.ref, snapshot.key, MODULAR_DEPRECATION_ARG); + this._ref = (reference.ref.child as any).call( + reference.ref, + snapshot.key, + MODULAR_DEPRECATION_ARG, + ) as DatabaseReference; } else { - this._ref = reference; + this._ref = reference as DatabaseReference; } // TODO #894 @@ -43,11 +62,11 @@ export default class DatabaseDataSnapshot { // } } - get key() { + get key(): string | null { return this._snapshot.key; } - get ref() { + get ref(): DatabaseReference { return this._ref; } @@ -56,22 +75,26 @@ export default class DatabaseDataSnapshot { * @param path * @returns {DatabaseDataSnapshot} */ - child(path) { + child(path: string): DatabaseDataSnapshot { if (!isString(path)) { throw new Error("snapshot().child(*) 'path' must be a string value"); } - let value = deepGet(this._snapshot.value, path); + let value = deepGet(this._snapshot.value as Record | unknown[], path); if (value === undefined) { value = null; } - const childRef = this._ref.child.call(this._ref, path, MODULAR_DEPRECATION_ARG); + const childRef = (this._ref.child as any).call( + this._ref, + path, + MODULAR_DEPRECATION_ARG, + ) as DatabaseReference; - let childPriority = null; + let childPriority: string | number | null = null; if (this._snapshot.childPriorities) { - const childPriorityValue = this._snapshot.childPriorities[childRef.key]; + const childPriorityValue = this._snapshot.childPriorities[childRef.key || '']; if (isString(childPriorityValue) || isNumber(childPriorityValue)) { childPriority = childPriorityValue; } @@ -84,7 +107,7 @@ export default class DatabaseDataSnapshot { childKeys: isObject(value) ? Object.keys(value) : [], priority: childPriority, }), - ); + ) as DatabaseDataSnapshot; } /** @@ -92,7 +115,7 @@ export default class DatabaseDataSnapshot { * * @returns {(function())|((path: PathLike, callback: (exists: boolean) => void) => void)|boolean|exists|(() => boolean)} */ - exists() { + exists(): boolean { return this._snapshot.exists; } @@ -101,7 +124,7 @@ export default class DatabaseDataSnapshot { * * @returns {{'.priority': *, '.value': *}} */ - exportVal() { + exportVal(): { '.value': unknown; '.priority': string | number | null } { let { value } = this._snapshot; if (isObject(value) || isArray(value)) { @@ -120,7 +143,7 @@ export default class DatabaseDataSnapshot { * @param action * @return {boolean} */ - forEach(action) { + forEach(action: (snapshot: DatabaseDataSnapshot, index: number) => true | undefined): boolean { if (!isFunction(action)) { throw new Error("snapshot.forEach(*) 'action' must be a function."); } @@ -141,7 +164,7 @@ export default class DatabaseDataSnapshot { for (let i = 0; i < this._snapshot.childKeys.length; i++) { const key = this._snapshot.childKeys[i]; - const snapshot = this.child.call(this, key, MODULAR_DEPRECATION_ARG); + const snapshot = (this.child as any).call(this, key, MODULAR_DEPRECATION_ARG); const actionReturn = action(snapshot, i); if (actionReturn === true) { @@ -153,7 +176,7 @@ export default class DatabaseDataSnapshot { return cancelled; } - getPriority() { + getPriority(): string | number | null { return this._snapshot.priority; } @@ -163,12 +186,12 @@ export default class DatabaseDataSnapshot { * @param path * @returns {boolean} */ - hasChild(path) { + hasChild(path: string): boolean { if (!isString(path)) { throw new Error("snapshot.hasChild(*) 'path' must be a string value."); } - return deepGet(this._snapshot.value, path) !== undefined; + return deepGet(this._snapshot.value as Record | unknown[], path) !== undefined; } /** @@ -176,7 +199,7 @@ export default class DatabaseDataSnapshot { * * @returns {boolean} */ - hasChildren() { + hasChildren(): boolean { return this.numChildren() > 0; } @@ -185,7 +208,7 @@ export default class DatabaseDataSnapshot { * * @returns {number} */ - numChildren() { + numChildren(): number { const { value } = this._snapshot; if (isArray(value)) { return value.length; @@ -201,7 +224,7 @@ export default class DatabaseDataSnapshot { * Same as snapshot.val() * @returns {any} */ - toJSON() { + toJSON(): unknown { return this.val(); } @@ -210,7 +233,7 @@ export default class DatabaseDataSnapshot { * * @returns {any} */ - val() { + val(): unknown { const { value } = this._snapshot; if (isObject(value) || isArray(value)) { diff --git a/packages/database/lib/DatabaseOnDisconnect.js b/packages/database/lib/DatabaseOnDisconnect.ts similarity index 86% rename from packages/database/lib/DatabaseOnDisconnect.js rename to packages/database/lib/DatabaseOnDisconnect.ts index 58943b606c..e24bf7ff54 100644 --- a/packages/database/lib/DatabaseOnDisconnect.js +++ b/packages/database/lib/DatabaseOnDisconnect.ts @@ -25,16 +25,19 @@ import { isValidPath, promiseWithOptionalCallback, } from '@react-native-firebase/app/dist/module/common'; +import type DatabaseReference from './DatabaseReference'; export default class DatabaseOnDisconnect { - constructor(reference) { + private _ref: DatabaseReference; + + constructor(reference: DatabaseReference) { this._ref = reference; } /** * @url https://firebase.google.com/docs/reference/js/firebase.database.OnDisconnect#cancel */ - cancel(onComplete) { + cancel(onComplete?: (error: Error | null) => void): Promise { if (!isUndefined(onComplete) && !isFunction(onComplete)) { throw new Error( "firebase.database().ref().onDisconnect().cancel(*) 'onComplete' must be a function if provided.", @@ -50,7 +53,7 @@ export default class DatabaseOnDisconnect { /** * @url https://firebase.google.com/docs/reference/js/firebase.database.OnDisconnect#remove */ - remove(onComplete) { + remove(onComplete?: (error: Error | null) => void): Promise { if (!isUndefined(onComplete) && !isFunction(onComplete)) { throw new Error( "firebase.database().ref().onDisconnect().remove(*) 'onComplete' must be a function if provided.", @@ -66,7 +69,7 @@ export default class DatabaseOnDisconnect { /** * @url https://firebase.google.com/docs/reference/js/firebase.database.OnDisconnect#set */ - set(value, onComplete) { + set(value: unknown, onComplete?: (error: Error | null) => void): Promise { if (isUndefined(value)) { throw new Error("firebase.database().ref().value(*) 'value' must be defined."); } @@ -86,7 +89,11 @@ export default class DatabaseOnDisconnect { /** * @url https://firebase.google.com/docs/reference/js/firebase.database.OnDisconnect#setwithpriority */ - setWithPriority(value, priority, onComplete) { + setWithPriority( + value: unknown, + priority: string | number | null, + onComplete?: (error: Error | null) => void, + ): Promise { if (isUndefined(value)) { throw new Error("firebase.database().ref().setWithPriority(*) 'value' must be defined."); } @@ -112,7 +119,10 @@ export default class DatabaseOnDisconnect { /** * @url https://firebase.google.com/docs/reference/js/firebase.database.OnDisconnect#update */ - update(values, onComplete) { + update( + values: { [key: string]: unknown }, + onComplete?: (error: Error | null) => void, + ): Promise { if (!isObject(values)) { throw new Error( "firebase.database().ref().onDisconnect().update(*) 'values' must be an object.", @@ -127,7 +137,7 @@ export default class DatabaseOnDisconnect { const keys = Object.keys(values); for (let i = 0; i < keys.length; i++) { - if (!isValidPath(keys[i])) { + if (!isValidPath(keys[i]!)) { throw new Error( 'firebase.database().onDisconnect().update(*) \'values\' contains an invalid path. Paths must be non-empty strings and can\'t contain ".", "#", "$", "[", or "]"', ); diff --git a/packages/database/lib/DatabaseQuery.js b/packages/database/lib/DatabaseQuery.ts similarity index 70% rename from packages/database/lib/DatabaseQuery.js rename to packages/database/lib/DatabaseQuery.ts index 6d54b63b3e..c867643499 100644 --- a/packages/database/lib/DatabaseQuery.js +++ b/packages/database/lib/DatabaseQuery.ts @@ -31,20 +31,33 @@ import { } from '@react-native-firebase/app/dist/module/common'; import DatabaseDataSnapshot from './DatabaseDataSnapshot'; import DatabaseSyncTree from './DatabaseSyncTree'; - -const eventTypes = ['value', 'child_added', 'child_changed', 'child_moved', 'child_removed']; +import DatabaseQueryModifiers from './DatabaseQueryModifiers'; +import type { DatabaseInternal } from './types/internal'; +import type { EventType, Reference } from './types/database'; +import type DatabaseReference from './DatabaseReference'; + +const eventTypes: EventType[] = [ + 'value', + 'child_added', + 'child_changed', + 'child_moved', + 'child_removed', +]; // To avoid React Native require cycle warnings -let DatabaseReference = null; -export function provideReferenceClass(databaseReference) { - DatabaseReference = databaseReference; +let DatabaseReferenceClass: typeof DatabaseReference | null = null; +export function provideReferenceClass(databaseReference: typeof DatabaseReference): void { + DatabaseReferenceClass = databaseReference; } // Internal listener count let listeners = 0; export default class DatabaseQuery extends ReferenceBase { - constructor(database, path, modifiers) { + protected _database: DatabaseInternal; + protected _modifiers: DatabaseQueryModifiers; + + constructor(database: DatabaseInternal, path: string, modifiers: DatabaseQueryModifiers) { super(path); this._database = database; this._modifiers = modifiers; @@ -53,8 +66,13 @@ export default class DatabaseQuery extends ReferenceBase { /** * @url https://firebase.google.com/docs/reference/js/firebase.database.Query.html#endat */ - get ref() { - return createDeprecationProxy(new DatabaseReference(this._database, this.path)); + get ref(): Reference { + if (!DatabaseReferenceClass) { + throw new Error('DatabaseReference class not provided. Call provideReferenceClass first.'); + } + return createDeprecationProxy( + new DatabaseReferenceClass(this._database, this.path), + ) as unknown as Reference; } /** @@ -63,7 +81,7 @@ export default class DatabaseQuery extends ReferenceBase { * @param key * @return {DatabaseQuery} */ - endAt(value, key) { + endAt(value: number | string | boolean | null, key?: string): DatabaseQuery { if (!isNumber(value) && !isString(value) && !isBoolean(value) && !isNull(value)) { throw new Error( "firebase.database().ref().endAt(*) 'value' must be a number, string, boolean or null value.", @@ -85,7 +103,9 @@ export default class DatabaseQuery extends ReferenceBase { const modifiers = this._modifiers._copy().endAt(value, key); modifiers.validateModifiers('firebase.database().ref().endAt()'); - return createDeprecationProxy(new DatabaseQuery(this._database, this.path, modifiers)); + return createDeprecationProxy( + new DatabaseQuery(this._database, this.path, modifiers), + ) as DatabaseQuery; } /** @@ -94,7 +114,7 @@ export default class DatabaseQuery extends ReferenceBase { * @param key * @return {DatabaseQuery} */ - equalTo(value, key) { + equalTo(value: number | string | boolean | null, key?: string): DatabaseQuery { if (!isNumber(value) && !isString(value) && !isBoolean(value) && !isNull(value)) { throw new Error( "firebase.database().ref().equalTo(*) 'value' must be a number, string, boolean or null value.", @@ -120,7 +140,13 @@ export default class DatabaseQuery extends ReferenceBase { } // Internal method calls should always use MODULAR_DEPRECATION_ARG to avoid false deprecation warnings - return this.startAt + return ( + this.startAt as ( + value: number | string | boolean | null, + key?: string, + ...args: unknown[] + ) => DatabaseQuery + ) .call(this, value, key, MODULAR_DEPRECATION_ARG) .endAt.call(this, value, MODULAR_DEPRECATION_ARG); } @@ -130,7 +156,7 @@ export default class DatabaseQuery extends ReferenceBase { * @param other * @return {boolean} */ - isEqual(other) { + isEqual(other: DatabaseQuery): boolean { if (!(other instanceof DatabaseQuery)) { throw new Error("firebase.database().ref().isEqual(*) 'other' must be an instance of Query."); } @@ -147,7 +173,7 @@ export default class DatabaseQuery extends ReferenceBase { * @param limit * @return {DatabaseQuery} */ - limitToFirst(limit) { + limitToFirst(limit: number): DatabaseQuery { if (this._modifiers.isValidLimit(limit)) { throw new Error( "firebase.database().ref().limitToFirst(*) 'limit' must be a positive integer value.", @@ -162,7 +188,7 @@ export default class DatabaseQuery extends ReferenceBase { return createDeprecationProxy( new DatabaseQuery(this._database, this.path, this._modifiers._copy().limitToFirst(limit)), - ); + ) as DatabaseQuery; } /** @@ -170,7 +196,7 @@ export default class DatabaseQuery extends ReferenceBase { * @param limit * @return {DatabaseQuery} */ - limitToLast(limit) { + limitToLast(limit: number): DatabaseQuery { if (this._modifiers.isValidLimit(limit)) { throw new Error( "firebase.database().ref().limitToLast(*) 'limit' must be a positive integer value.", @@ -185,7 +211,7 @@ export default class DatabaseQuery extends ReferenceBase { return createDeprecationProxy( new DatabaseQuery(this._database, this.path, this._modifiers._copy().limitToLast(limit)), - ); + ) as DatabaseQuery; } /** @@ -195,7 +221,11 @@ export default class DatabaseQuery extends ReferenceBase { * @param context * @return {DatabaseQuery} */ - off(eventType, callback, context) { + off( + eventType?: EventType, + callback?: (snapshot: DatabaseDataSnapshot, previousChildKey?: string | null) => void, + context?: Record, + ): number { // if (arguments.length === 0) { // Firebase Docs: @@ -233,7 +263,7 @@ export default class DatabaseQuery extends ReferenceBase { callback, ); if (!registration) { - return []; + return 0; } // remove the paired cancellation registration if any exist @@ -241,15 +271,18 @@ export default class DatabaseQuery extends ReferenceBase { // remove only the first registration to match firebase web sdk // call multiple times to remove multiple registrations - return DatabaseSyncTree.removeListenerRegistrations(callback, [registration]); + return DatabaseSyncTree.removeListenerRegistrations(callback, [registration]).length; } // Firebase Docs: // If a callback is not specified, all callbacks for the specified eventType will be removed. - const registrations = DatabaseSyncTree.getRegistrationsByPathEvent(this.path, eventType); + const registrations = DatabaseSyncTree.getRegistrationsByPathEvent(this.path, eventType!); DatabaseSyncTree.removeListenersForRegistrations( - DatabaseSyncTree.getRegistrationsByPathEvent(this.path, `${eventType}$cancelled`), + DatabaseSyncTree.getRegistrationsByPathEvent( + this.path, + `${eventType}$cancelled` as EventType, + ), ); return DatabaseSyncTree.removeListenersForRegistrations(registrations); @@ -263,7 +296,12 @@ export default class DatabaseQuery extends ReferenceBase { * @param context * @return {DatabaseQuery} */ - on(eventType, callback, cancelCallbackOrContext, context) { + on( + eventType: EventType, + callback: (snapshot: DatabaseDataSnapshot, previousChildKey?: string | null) => void, + cancelCallbackOrContext?: ((error: Error) => void) | Record, + context?: Record, + ): (snapshot: DatabaseDataSnapshot, previousChildKey?: string | null) => void { if (!eventTypes.includes(eventType)) { throw new Error( `firebase.database().ref().on(*) 'eventType' must be one of ${eventTypes.join(', ')}.`, @@ -299,7 +337,7 @@ export default class DatabaseQuery extends ReferenceBase { // Add a new SyncTree registration DatabaseSyncTree.addRegistration({ eventType, - ref: this.ref, + ref: this.ref as any, path: this.path, key: queryKey, appName: this._database.app.name, @@ -314,15 +352,17 @@ export default class DatabaseQuery extends ReferenceBase { // to occur either, only happens on failure to register on native DatabaseSyncTree.addRegistration({ - ref: this.ref, + ref: this.ref as any, once: true, path: this.path, key: queryKey, appName: this._database.app.name, dbURL: this._database._customUrlOrRegion, - eventType: `${eventType}$cancelled`, + eventType: `${eventType}$cancelled` as EventType, eventRegistrationKey: registrationCancellationKey, - listener: _context ? cancelCallbackOrContext.bind(_context) : cancelCallbackOrContext, + listener: _context + ? (cancelCallbackOrContext as any).bind(_context) + : (cancelCallbackOrContext as any), }); } @@ -330,7 +370,6 @@ export default class DatabaseQuery extends ReferenceBase { eventType, path: this.path, key: queryKey, - appName: this._database.app.name, modifiers: this._modifiers.toArray(), hasCancellationCallback: isFunction(cancelCallbackOrContext), registration: { @@ -353,7 +392,12 @@ export default class DatabaseQuery extends ReferenceBase { * @param failureCallbackOrContext * @param context */ - once(eventType, successCallBack, failureCallbackOrContext, context) { + once( + eventType: EventType, + successCallBack?: (snapshot: DatabaseDataSnapshot, previousChildKey?: string | null) => void, + failureCallbackOrContext?: ((error: Error) => void) | Record, + context?: Record, + ): Promise { if (!eventTypes.includes(eventType)) { throw new Error( `firebase.database().ref().once(*) 'eventType' must be one of ${eventTypes.join(', ')}.`, @@ -384,33 +428,63 @@ export default class DatabaseQuery extends ReferenceBase { return this._database.native .once(this.path, modifiers, eventType) - .then(result => { - let dataSnapshot; - let previousChildName; - - // Child based events return a previousChildName - if (eventType === 'value') { - dataSnapshot = createDeprecationProxy(new DatabaseDataSnapshot(this.ref, result)); - } else { - dataSnapshot = createDeprecationProxy( - new DatabaseDataSnapshot(this.ref, result.snapshot), - ); - previousChildName = result.previousChildName; - } - - if (isFunction(successCallBack)) { - if (isObject(failureCallbackOrContext)) { - successCallBack.bind(failureCallbackOrContext)(dataSnapshot, previousChildName); - } else if (isObject(context)) { - successCallBack.bind(context)(dataSnapshot, previousChildName); + .then( + (result: { + snapshot?: unknown; + previousChildName?: string | null; + value?: unknown; + key?: string | null; + exists?: boolean; + childKeys?: string[]; + priority?: string | number | null; + }) => { + let dataSnapshot: DatabaseDataSnapshot; + let previousChildName: string | null | undefined; + + // Child based events return a previousChildName + if (eventType === 'value') { + dataSnapshot = createDeprecationProxy( + new DatabaseDataSnapshot( + this.ref as any, + result as { + value: unknown; + key: string | null; + exists: boolean; + childKeys: string[]; + priority: string | number | null; + }, + ), + ) as DatabaseDataSnapshot; } else { - successCallBack(dataSnapshot, previousChildName); + dataSnapshot = createDeprecationProxy( + new DatabaseDataSnapshot( + this.ref as any, + (result.snapshot as { + value: unknown; + key: string | null; + exists: boolean; + childKeys: string[]; + priority: string | number | null; + }) || { value: null, key: null, exists: false, childKeys: [], priority: null }, + ), + ) as DatabaseDataSnapshot; + previousChildName = result.previousChildName; } - } - return dataSnapshot; - }) - .catch(error => { + if (isFunction(successCallBack)) { + if (isObject(failureCallbackOrContext)) { + successCallBack.bind(failureCallbackOrContext)(dataSnapshot, previousChildName); + } else if (isObject(context)) { + successCallBack.bind(context)(dataSnapshot, previousChildName); + } else { + successCallBack(dataSnapshot, previousChildName); + } + } + + return dataSnapshot; + }, + ) + .catch((error: Error) => { if (isFunction(failureCallbackOrContext)) { failureCallbackOrContext(error); } @@ -421,7 +495,7 @@ export default class DatabaseQuery extends ReferenceBase { /** * @url https://firebase.google.com/docs/reference/js/firebase.database.Query.html#orderbychild */ - orderByChild(path) { + orderByChild(path: string): DatabaseQuery { if (!isString(path)) { throw new Error("firebase.database().ref().orderByChild(*) 'path' must be a string value."); } @@ -441,13 +515,15 @@ export default class DatabaseQuery extends ReferenceBase { const modifiers = this._modifiers._copy().orderByChild(path); modifiers.validateModifiers('firebase.database().ref().orderByChild()'); - return createDeprecationProxy(new DatabaseQuery(this._database, this.path, modifiers)); + return createDeprecationProxy( + new DatabaseQuery(this._database, this.path, modifiers), + ) as DatabaseQuery; } /** * @url https://firebase.google.com/docs/reference/js/firebase.database.Query.html#orderbykey */ - orderByKey() { + orderByKey(): DatabaseQuery { if (this._modifiers.hasOrderBy()) { throw new Error( "firebase.database().ref().orderByKey() You can't combine multiple orderBy calls.", @@ -457,13 +533,15 @@ export default class DatabaseQuery extends ReferenceBase { const modifiers = this._modifiers._copy().orderByKey(); modifiers.validateModifiers('firebase.database().ref().orderByKey()'); - return createDeprecationProxy(new DatabaseQuery(this._database, this.path, modifiers)); + return createDeprecationProxy( + new DatabaseQuery(this._database, this.path, modifiers), + ) as DatabaseQuery; } /** * @url https://firebase.google.com/docs/reference/js/firebase.database.Query.html#orderbypriority */ - orderByPriority() { + orderByPriority(): DatabaseQuery { if (this._modifiers.hasOrderBy()) { throw new Error( "firebase.database().ref().orderByPriority() You can't combine multiple orderBy calls.", @@ -473,13 +551,15 @@ export default class DatabaseQuery extends ReferenceBase { const modifiers = this._modifiers._copy().orderByPriority(); modifiers.validateModifiers('firebase.database().ref().orderByPriority()'); - return createDeprecationProxy(new DatabaseQuery(this._database, this.path, modifiers)); + return createDeprecationProxy( + new DatabaseQuery(this._database, this.path, modifiers), + ) as DatabaseQuery; } /** * @url https://firebase.google.com/docs/reference/js/firebase.database.Query.html#orderbyvalue */ - orderByValue() { + orderByValue(): DatabaseQuery { if (this._modifiers.hasOrderBy()) { throw new Error( "firebase.database().ref().orderByValue() You can't combine multiple orderBy calls.", @@ -489,10 +569,12 @@ export default class DatabaseQuery extends ReferenceBase { const modifiers = this._modifiers._copy().orderByValue(); modifiers.validateModifiers('firebase.database().ref().orderByValue()'); - return createDeprecationProxy(new DatabaseQuery(this._database, this.path, modifiers)); + return createDeprecationProxy( + new DatabaseQuery(this._database, this.path, modifiers), + ) as DatabaseQuery; } - startAt(value, key) { + startAt(value?: number | string | boolean | null, key?: string): DatabaseQuery { if (!isNumber(value) && !isString(value) && !isBoolean(value) && !isNull(value)) { throw new Error( "firebase.database().ref().startAt(*) 'value' must be a number, string, boolean or null value.", @@ -511,21 +593,23 @@ export default class DatabaseQuery extends ReferenceBase { ); } - const modifiers = this._modifiers._copy().startAt(value, key); + const modifiers = this._modifiers._copy().startAt(value!, key); modifiers.validateModifiers('firebase.database().ref().startAt()'); - return createDeprecationProxy(new DatabaseQuery(this._database, this.path, modifiers)); + return createDeprecationProxy( + new DatabaseQuery(this._database, this.path, modifiers), + ) as DatabaseQuery; } - toJSON() { + toJSON(): string { return this.toString(); } - toString() { + toString(): string { return `${this._database._customUrlOrRegion}${pathToUrlEncodedString(this.path)}`; } - keepSynced(bool) { + keepSynced(bool: boolean): Promise { if (!isBoolean(bool)) { throw new Error( "firebase.database().ref().keepSynced(*) 'bool' value must be a boolean value.", @@ -542,14 +626,14 @@ export default class DatabaseQuery extends ReferenceBase { // Generates a unique string for a query // Ensures any queries called in various orders keep the same key - _generateQueryKey() { + _generateQueryKey(): string { return `$${this._database._customUrlOrRegion}$/${this.path}$${ this._database.app.name }$${this._modifiers.toString()}`; } // Generates a unique event registration key - _generateQueryEventKey(eventType) { + _generateQueryEventKey(eventType: EventType): string { return `${this._generateQueryKey()}$${listeners}$${eventType}`; } } diff --git a/packages/database/lib/DatabaseQueryModifiers.js b/packages/database/lib/DatabaseQueryModifiers.ts similarity index 69% rename from packages/database/lib/DatabaseQueryModifiers.js rename to packages/database/lib/DatabaseQueryModifiers.ts index c3ac57eedb..1b5ecf08c5 100644 --- a/packages/database/lib/DatabaseQueryModifiers.js +++ b/packages/database/lib/DatabaseQueryModifiers.ts @@ -20,9 +20,41 @@ import { isNull, isNumber, isString } from '@react-native-firebase/app/dist/modu const CONSTANTS = { VIEW_FROM_LEFT: 'left', VIEW_FROM_RIGHT: 'right', -}; +} as const; + +interface LimitModifier { + id: string; + name: 'limitToFirst' | 'limitToLast'; + type: 'limit'; + value: number; + viewFrom: typeof CONSTANTS.VIEW_FROM_LEFT | typeof CONSTANTS.VIEW_FROM_RIGHT; +} + +interface OrderModifier { + id: string; + type: 'orderBy'; + name: 'orderByChild' | 'orderByKey' | 'orderByPriority' | 'orderByValue'; + key?: string; +} + +interface FilterModifier { + id: string; + type: 'filter'; + name: 'startAt' | 'endAt'; + value: number | string | boolean | null; + valueType: 'null' | 'string' | 'number' | 'boolean'; + key?: string; +} + +type Modifier = LimitModifier | OrderModifier | FilterModifier; export default class DatabaseQueryModifiers { + private _limit: LimitModifier | undefined; + private _orderBy: OrderModifier | undefined; + private _startAt: FilterModifier | undefined; + private _endAt: FilterModifier | undefined; + private _modifiers: Modifier[]; + constructor() { this._limit = undefined; this._orderBy = undefined; @@ -31,7 +63,7 @@ export default class DatabaseQueryModifiers { this._modifiers = []; } - _copy() { + _copy(): DatabaseQueryModifiers { const newInstance = new DatabaseQueryModifiers(); newInstance._limit = this._limit; newInstance._orderBy = this._orderBy; @@ -47,16 +79,16 @@ export default class DatabaseQueryModifiers { * */ - hasLimit() { + hasLimit(): boolean { return this._limit !== undefined; } - isValidLimit(limit) { + isValidLimit(limit: unknown): boolean { return !isNumber(limit) || Math.floor(limit) !== limit || limit <= 0; } - limitToFirst(limit) { - const newLimit = { + limitToFirst(limit: number): this { + const newLimit: LimitModifier = { id: `limit-limitToFirst:${limit}`, name: 'limitToFirst', type: 'limit', @@ -69,8 +101,8 @@ export default class DatabaseQueryModifiers { return this; } - limitToLast(limit) { - const newLimit = { + limitToLast(limit: number): this { + const newLimit: LimitModifier = { id: `limit-limitToLast:${limit}`, name: 'limitToLast', type: 'limit', @@ -89,12 +121,12 @@ export default class DatabaseQueryModifiers { * */ - hasOrderBy() { + hasOrderBy(): boolean { return this._orderBy !== undefined; } - orderByChild(path) { - const newOrder = { + orderByChild(path: string): this { + const newOrder: OrderModifier = { id: `order-orderByChild:${path}`, type: 'orderBy', name: 'orderByChild', @@ -106,8 +138,8 @@ export default class DatabaseQueryModifiers { return this; } - orderByKey() { - const newOrder = { + orderByKey(): this { + const newOrder: OrderModifier = { id: 'order-orderByKey', type: 'orderBy', name: 'orderByKey', @@ -118,12 +150,12 @@ export default class DatabaseQueryModifiers { return this; } - isValidPriority(priority) { + isValidPriority(priority: unknown): boolean { return isNumber(priority) || isString(priority) || isNull(priority); } - orderByPriority() { - const newOrder = { + orderByPriority(): this { + const newOrder: OrderModifier = { id: 'order-orderByPriority', type: 'orderBy', name: 'orderByPriority', @@ -134,8 +166,8 @@ export default class DatabaseQueryModifiers { return this; } - orderByValue() { - const newOrder = { + orderByValue(): this { + const newOrder: OrderModifier = { id: 'order-orderByValue', type: 'orderBy', name: 'orderByValue', @@ -152,21 +184,21 @@ export default class DatabaseQueryModifiers { * */ - hasStartAt() { + hasStartAt(): boolean { return this._startAt !== undefined; } - hasEndAt() { + hasEndAt(): boolean { return this._endAt !== undefined; } - startAt(value, key) { - const newStart = { + startAt(value: number | string | boolean | null, key?: string): this { + const newStart: FilterModifier = { id: `filter-startAt:${value}:${key || ''}`, type: 'filter', name: 'startAt', value, - valueType: value === null ? 'null' : typeof value, + valueType: value === null ? 'null' : (typeof value as 'string' | 'number' | 'boolean'), key, }; @@ -175,29 +207,29 @@ export default class DatabaseQueryModifiers { return this; } - endAt(value, key) { - const newStart = { + endAt(value: number | string | boolean | null, key?: string): this { + const newEnd: FilterModifier = { id: `filter-endAt:${value}:${key || ''}`, type: 'filter', name: 'endAt', value, - valueType: value === null ? 'null' : typeof value, + valueType: value === null ? 'null' : (typeof value as 'string' | 'number' | 'boolean'), key, }; - this._endAt = newStart; - this._modifiers.push(newStart); + this._endAt = newEnd; + this._modifiers.push(newEnd); return this; } // Returns a modifier array - toArray() { + toArray(): Modifier[] { return this._modifiers; } // Converts the modifier list to a string representation - toString() { - const sorted = [].concat(this._modifiers).sort((a, b) => { + toString(): string { + const sorted = [...this._modifiers].sort((a, b) => { if (a.id < b.id) { return -1; } @@ -212,13 +244,13 @@ export default class DatabaseQueryModifiers { if (i !== 0) { key += ','; } - key += sorted[i].id; + key += sorted[i]!.id; } key += '}'; return key; } - validateModifiers(prefix) { + validateModifiers(prefix: string): void { if (this._orderBy && this._orderBy.name === 'orderByKey') { if ((this._startAt && !!this._startAt.key) || (this._endAt && !!this._endAt.key)) { throw new Error( diff --git a/packages/database/lib/DatabaseReference.js b/packages/database/lib/DatabaseReference.ts similarity index 77% rename from packages/database/lib/DatabaseReference.js rename to packages/database/lib/DatabaseReference.ts index d1bee50818..fce317fc6a 100644 --- a/packages/database/lib/DatabaseReference.js +++ b/packages/database/lib/DatabaseReference.ts @@ -40,11 +40,15 @@ import DatabaseQueryModifiers from './DatabaseQueryModifiers'; import DatabaseThenableReference, { provideReferenceClass as provideReferenceClassForThenable, } from './DatabaseThenableReference'; +import type { DatabaseInternal } from './types/internal'; +import type { TransactionResult } from './types/database'; const internalRefs = ['.info/connected', '.info/serverTimeOffset']; export default class DatabaseReference extends DatabaseQuery { - constructor(database, path) { + _database: DatabaseInternal; + + constructor(database: DatabaseInternal, path: string) { // Validate the reference path if (!internalRefs.includes(path) && !isValidPath(path)) { throw new Error( @@ -59,32 +63,34 @@ export default class DatabaseReference extends DatabaseQuery { /** * @url https://firebase.google.com/docs/reference/js/firebase.database.Reference.html#parent */ - get parent() { + get parent(): DatabaseReference | null { const parentPath = pathParent(this.path); if (parentPath === null) { return null; } - return createDeprecationProxy(new DatabaseReference(this._database, parentPath)); + return createDeprecationProxy( + new DatabaseReference(this._database, parentPath), + ) as DatabaseReference; } /** * @url https://firebase.google.com/docs/reference/js/firebase.database.Reference.html#root */ - get root() { - return createDeprecationProxy(new DatabaseReference(this._database, '/')); + get root(): DatabaseReference { + return createDeprecationProxy(new DatabaseReference(this._database, '/')) as DatabaseReference; } /** * @url https://firebase.google.com/docs/reference/js/firebase.database.Reference.html#child * @param path */ - child(path) { + child(path: string): DatabaseReference { if (!isString(path)) { throw new Error("firebase.database().ref().child(*) 'path' must be a string value."); } return createDeprecationProxy( new DatabaseReference(this._database, pathChild(this.path, path)), - ); + ) as DatabaseReference; } /** @@ -92,7 +98,7 @@ export default class DatabaseReference extends DatabaseQuery { * @param value * @param onComplete */ - set(value, onComplete) { + set(value: unknown, onComplete?: (error: Error | null) => void): Promise { if (isUndefined(value)) { throw new Error("firebase.database().ref().set(*) 'value' must be defined."); } @@ -111,14 +117,17 @@ export default class DatabaseReference extends DatabaseQuery { * @param values * @param onComplete */ - update(values, onComplete) { + update( + values: { [key: string]: unknown }, + onComplete?: (error: Error | null) => void, + ): Promise { if (!isObject(values)) { throw new Error("firebase.database().ref().update(*) 'values' must be an object."); } const keys = Object.keys(values); for (let i = 0; i < keys.length; i++) { - if (!isValidPath(keys[i])) { + if (!isValidPath(keys[i]!)) { throw new Error( 'firebase.database().update(*) \'values\' contains an invalid path. Paths must be non-empty strings and can\'t contain ".", "#", "$", "[", or "]"', ); @@ -143,7 +152,11 @@ export default class DatabaseReference extends DatabaseQuery { * @param newPriority * @param onComplete */ - setWithPriority(newVal, newPriority, onComplete) { + setWithPriority( + newVal: unknown, + newPriority: string | number | null, + onComplete?: (error: Error | null) => void, + ): Promise { if (isUndefined(newVal)) { throw new Error("firebase.database().ref().setWithPriority(*) 'newVal' must be defined."); } @@ -173,7 +186,7 @@ export default class DatabaseReference extends DatabaseQuery { * @url https://firebase.google.com/docs/reference/js/firebase.database.Reference#remove * @param onComplete */ - remove(onComplete) { + remove(onComplete?: (error: Error | null) => void): Promise { if (!isUndefined(onComplete) && !isFunction(onComplete)) { throw new Error( "firebase.database().ref().remove(*) 'onComplete' must be a function if provided.", @@ -189,7 +202,15 @@ export default class DatabaseReference extends DatabaseQuery { * @param onComplete * @param applyLocally */ - transaction(transactionUpdate, onComplete, applyLocally) { + transaction( + transactionUpdate: (currentData: unknown) => unknown, + onComplete?: ( + error: Error | null, + committed: boolean, + snapshot: DatabaseDataSnapshot | null, + ) => void, + applyLocally?: boolean, + ): Promise { if (!isFunction(transactionUpdate)) { throw new Error( "firebase.database().ref().transaction(*) 'transactionUpdate' must be a function.", @@ -209,7 +230,11 @@ export default class DatabaseReference extends DatabaseQuery { } return new Promise((resolve, reject) => { - const onCompleteWrapper = (error, committed, snapshotData) => { + const onCompleteWrapper = ( + error: Error | null, + committed: boolean, + snapshotData: DatabaseDataSnapshot | null, + ) => { if (isFunction(onComplete)) { if (error) { onComplete(error, committed, null); @@ -217,7 +242,11 @@ export default class DatabaseReference extends DatabaseQuery { onComplete( null, committed, - createDeprecationProxy(new DatabaseDataSnapshot(this, snapshotData)), + snapshotData + ? (createDeprecationProxy( + new DatabaseDataSnapshot(this, snapshotData as any), + ) as DatabaseDataSnapshot) + : null, ); } } @@ -227,7 +256,11 @@ export default class DatabaseReference extends DatabaseQuery { } return resolve({ committed, - snapshot: createDeprecationProxy(new DatabaseDataSnapshot(this, snapshotData)), + snapshot: snapshotData + ? (createDeprecationProxy( + new DatabaseDataSnapshot(this, snapshotData as any), + ) as DatabaseDataSnapshot) + : (null as any), }); }; @@ -241,7 +274,10 @@ export default class DatabaseReference extends DatabaseQuery { * @param priority * @param onComplete */ - setPriority(priority, onComplete) { + setPriority( + priority: string | number | null, + onComplete?: (error: Error | null) => void, + ): Promise { if (!isNumber(priority) && !isString(priority) && !isNull(priority)) { throw new Error( "firebase.database().ref().setPriority(*) 'priority' must be a number, string or null value.", @@ -266,7 +302,7 @@ export default class DatabaseReference extends DatabaseQuery { * @param onComplete * @returns {DatabaseReference} */ - push(value, onComplete) { + push(value?: unknown, onComplete?: () => void): DatabaseThenableReference { if (!isUndefined(onComplete) && !isFunction(onComplete)) { throw new Error( "firebase.database().ref().push(_, *) 'onComplete' must be a function if provided.", @@ -279,13 +315,29 @@ export default class DatabaseReference extends DatabaseQuery { return new DatabaseThenableReference( this._database, pathChild(this.path, id), - Promise.resolve(this.child.call(this, id, MODULAR_DEPRECATION_ARG)), + Promise.resolve( + (this.child as (path: string, ...args: unknown[]) => DatabaseReference).call( + this, + id, + MODULAR_DEPRECATION_ARG, + ), + ), ); } - const pushRef = this.child.call(this, id, MODULAR_DEPRECATION_ARG); + const pushRef = (this.child as (path: string, ...args: unknown[]) => DatabaseReference).call( + this, + id, + MODULAR_DEPRECATION_ARG, + ); - const promise = pushRef.set + const promise = ( + pushRef.set as ( + value: unknown, + onComplete?: (error: Error | null) => void, + ...args: unknown[] + ) => Promise + ) .call(pushRef, value, onComplete, MODULAR_DEPRECATION_ARG) .then(() => pushRef); @@ -300,7 +352,7 @@ export default class DatabaseReference extends DatabaseQuery { /** * @url https://firebase.google.com/docs/reference/js/firebase.database.Reference#ondisconnect */ - onDisconnect() { + onDisconnect(): DatabaseOnDisconnect { return new DatabaseOnDisconnect(this); } } diff --git a/packages/database/lib/DatabaseStatics.js b/packages/database/lib/DatabaseStatics.ts similarity index 82% rename from packages/database/lib/DatabaseStatics.js rename to packages/database/lib/DatabaseStatics.ts index ce17a426e1..c7fcb27fd2 100644 --- a/packages/database/lib/DatabaseStatics.js +++ b/packages/database/lib/DatabaseStatics.ts @@ -15,17 +15,21 @@ * */ -export default { +import type { ServerValue } from './types/database'; + +const DatabaseStatics = { ServerValue: { TIMESTAMP: { '.sv': 'timestamp', }, - increment(delta) { + increment(delta: number): object { return { '.sv': { increment: delta, }, }; }, - }, + } as ServerValue, }; + +export default DatabaseStatics; diff --git a/packages/database/lib/DatabaseSyncTree.js b/packages/database/lib/DatabaseSyncTree.ts similarity index 59% rename from packages/database/lib/DatabaseSyncTree.js rename to packages/database/lib/DatabaseSyncTree.ts index be9babce81..42e7282edd 100644 --- a/packages/database/lib/DatabaseSyncTree.js +++ b/packages/database/lib/DatabaseSyncTree.ts @@ -20,8 +20,59 @@ import { getReactNativeModule } from '@react-native-firebase/app/dist/module/int import NativeError from '@react-native-firebase/app/dist/module/internal/NativeFirebaseError'; import SharedEventEmitter from '@react-native-firebase/app/dist/module/internal/SharedEventEmitter'; import DatabaseDataSnapshot from './DatabaseDataSnapshot'; +import type DatabaseReference from './DatabaseReference'; +import type { EventType } from './types/database'; + +interface Registration { + eventRegistrationKey: string; + registrationCancellationKey?: string; + eventType: EventType; + listener: (snapshot: DatabaseDataSnapshot, previousChildName?: string | null) => void; + once?: boolean; + path: string; + ref: DatabaseReference; + key: string; + appName?: string; + dbURL?: string; +} + +interface SyncEventBody { + error?: unknown; + registration?: { + eventRegistrationKey: string; + registrationCancellationKey?: string; + }; + key?: string; + eventRegistrationKey?: string; + eventType?: EventType; + data?: { + snapshot?: unknown; + previousChildName?: string | null; + }; +} + +interface SyncEvent { + body: SyncEventBody; +} class DatabaseSyncTree { + private _tree: Record< + string, + Record< + EventType, + Record void> + > + >; + private _reverseLookup: Record; + private _registry: Record< + string, + Set<{ + listener: (snapshot: DatabaseDataSnapshot, previousChildName?: string | null) => void; + remove: () => void; + context?: Record; + }> + >; + constructor() { this._tree = {}; this._reverseLookup = {}; @@ -38,12 +89,26 @@ class DatabaseSyncTree { SharedEventEmitter.addListener('database_sync_event', this._handleSyncEvent.bind(this)); } - get native() { + get native(): ReturnType { return getReactNativeModule('RNFBDatabaseQueryModule'); } // from upstream EventEmitter: initialize registrations for an emitter key - _allocate(registry, eventType) { + _allocate( + registry: Record< + string, + Set<{ + listener: (snapshot: DatabaseDataSnapshot, previousChildName?: string | null) => void; + remove: () => void; + context?: Record; + }> + >, + eventType: string, + ): Set<{ + listener: (snapshot: DatabaseDataSnapshot, previousChildName?: string | null) => void; + remove: () => void; + context?: Record; + }> { let registrations = registry[eventType]; if (registrations == null) { registrations = new Set(); @@ -57,7 +122,7 @@ class DatabaseSyncTree { * @param event * @private */ - _handleSyncEvent(event) { + _handleSyncEvent(event: SyncEvent): void { const { body } = event; if (body.error) { this._handleErrorEvent(body); @@ -72,16 +137,20 @@ class DatabaseSyncTree { * @param event * @private */ - _handleErrorEvent(event) { + _handleErrorEvent(event: SyncEventBody): void { // console.log('SyncTree.ERROR >>>', event); - const { eventRegistrationKey, registrationCancellationKey } = event.registration; + const { eventRegistrationKey, registrationCancellationKey } = event.registration || {}; + + if (!eventRegistrationKey || !registrationCancellationKey) { + return; + } const registration = this.getRegistration(registrationCancellationKey); if (registration) { // build a new js error - we additionally attach // the ref as a property for easier debugging - const error = NativeError.fromEvent(event.error, 'database'); + const error = NativeError.fromEvent(event.error as any, 'database'); // forward on to users .on(successCallback, cancellationCallback <-- listener SharedEventEmitter.emit(registrationCancellationKey, error); @@ -100,9 +169,14 @@ class DatabaseSyncTree { * @param event * @private */ - _handleValueEvent(event) { + _handleValueEvent(event: SyncEventBody): void { // console.log('SyncTree.VALUE >>>', event); - const { key, eventRegistrationKey } = event.registration; + const { key, eventType, registration: registrationData } = event; + const eventRegistrationKey = registrationData?.eventRegistrationKey; + if (!eventRegistrationKey || !key || !eventType) { + return; + } + const registration = this.getRegistration(eventRegistrationKey); // console.log('SyncTree.registration >>>', registration); @@ -111,20 +185,40 @@ class DatabaseSyncTree { // notify native that the registration // no longer exists so it can remove // the native listeners - return this.native.off(key, eventRegistrationKey); + return (this.native as any)?.off(key, eventRegistrationKey); } - let snapshot; - let previousChildName; + let snapshot: DatabaseDataSnapshot; + let previousChildName: string | null | undefined; // Value events don't return a previousChildName - if (event.eventType === 'value') { - snapshot = createDeprecationProxy(new DatabaseDataSnapshot(registration.ref, event.data)); + if (eventType === 'value') { + snapshot = createDeprecationProxy( + new DatabaseDataSnapshot( + registration.ref, + event.data as { + value: unknown; + key: string | null; + exists: boolean; + childKeys: string[]; + priority: string | number | null; + }, + ), + ) as DatabaseDataSnapshot; } else { snapshot = createDeprecationProxy( - new DatabaseDataSnapshot(registration.ref, event.data.snapshot), - ); - previousChildName = event.data.previousChildName; + new DatabaseDataSnapshot( + registration.ref, + (event.data?.snapshot as { + value: unknown; + key: string | null; + exists: boolean; + childKeys: string[]; + priority: string | number | null; + }) || { value: null, key: null, exists: false, childKeys: [], priority: null }, + ), + ) as DatabaseDataSnapshot; + previousChildName = event.data?.previousChildName; } // forward on to users .on(successCallback <-- listener @@ -137,7 +231,7 @@ class DatabaseSyncTree { * @param registration * @return {null} */ - getRegistration(registration) { + getRegistration(registration: string): Registration | null { return this._reverseLookup[registration] ? Object.assign({}, this._reverseLookup[registration]) : null; @@ -149,7 +243,7 @@ class DatabaseSyncTree { * @param registrations * @return {number} */ - removeListenersForRegistrations(registrations) { + removeListenersForRegistrations(registrations: string | string[]): number { if (isString(registrations)) { this.removeRegistration(registrations); SharedEventEmitter.removeAllListeners(registrations); @@ -167,13 +261,13 @@ class DatabaseSyncTree { return 0; } for (let i = 0, len = registrations.length; i < len; i++) { - this.removeRegistration(registrations[i]); - SharedEventEmitter.removeAllListeners(registrations[i]); + this.removeRegistration(registrations[i]!); + SharedEventEmitter.removeAllListeners(registrations[i]!); // mirror upstream accounting - clear out all registrations for this key if (registrations[i] == null) { this._registry = {}; } else { - delete this._registry[registrations[i]]; + delete this._registry[registrations[i]!]; } } @@ -187,15 +281,24 @@ class DatabaseSyncTree { * @param registrations * @return {Array} array of registrations removed */ - removeListenerRegistrations(listener, registrations) { + removeListenerRegistrations( + listener: (snapshot: DatabaseDataSnapshot, previousChildName?: string | null) => void, + registrations: string[], + ): string[] { if (!Array.isArray(registrations)) { return []; } - const removed = []; + const removed: string[] = []; for (let i = 0, len = registrations.length; i < len; i++) { - const registration = registrations[i]; - let subscriptions; + const registration = registrations[i]!; + let subscriptions: + | Array<{ + listener: (snapshot: DatabaseDataSnapshot, previousChildName?: string | null) => void; + remove: () => void; + context?: Record; + }> + | undefined; // get all registrations for this key so we can find our specific listener const registrySubscriptionsSet = this._registry[registration]; @@ -209,7 +312,7 @@ class DatabaseSyncTree { // The subscription may have been removed during this event loop. // its listener matches the listener in method parameters if (subscription && subscription.listener === listener) { - this._registry[registration].delete(subscription); + this._registry[registration]!.delete(subscription); subscription.remove(); removed.push(registration); this.removeRegistration(registration); @@ -227,12 +330,15 @@ class DatabaseSyncTree { * @param path * @return {Array} */ - getRegistrationsByPath(path) { - const out = []; + getRegistrationsByPath(path: string): string[] { + const out: string[] = []; const eventKeys = Object.keys(this._tree[path] || {}); for (let i = 0, len = eventKeys.length; i < len; i++) { - Array.prototype.push.apply(out, Object.keys(this._tree[path][eventKeys[i]])); + Array.prototype.push.apply( + out, + Object.keys(this._tree[path]![eventKeys[i]! as EventType] || {}), + ); } return out; @@ -245,15 +351,15 @@ class DatabaseSyncTree { * @param eventType * @return {Array} */ - getRegistrationsByPathEvent(path, eventType) { + getRegistrationsByPathEvent(path: string, eventType: EventType): string[] { if (!this._tree[path]) { return []; } - if (!this._tree[path][eventType]) { + if (!this._tree[path]![eventType]) { return []; } - return Object.keys(this._tree[path][eventType]); + return Object.keys(this._tree[path]![eventType]!); } /** @@ -264,20 +370,24 @@ class DatabaseSyncTree { * @param listener * @return {Array} */ - getOneByPathEventListener(path, eventType, listener) { + getOneByPathEventListener( + path: string, + eventType: EventType, + listener: (snapshot: DatabaseDataSnapshot, previousChildName?: string | null) => void, + ): string | null { if (!this._tree[path]) { return null; } - if (!this._tree[path][eventType]) { + if (!this._tree[path]![eventType]) { return null; } - const registrationsForPathEvent = Object.entries(this._tree[path][eventType]); + const registrationsForPathEvent = Object.entries(this._tree[path]![eventType]!); for (let i = 0; i < registrationsForPathEvent.length; i++) { - const registration = registrationsForPathEvent[i]; + const registration = registrationsForPathEvent[i]!; if (registration[1] === listener) { - return registration[0]; + return registration[0]!; } } @@ -289,31 +399,40 @@ class DatabaseSyncTree { * * @param registration */ - addRegistration(registration) { + addRegistration(registration: Registration): string { const { eventRegistrationKey, eventType, listener, once, path } = registration; if (!this._tree[path]) { - this._tree[path] = {}; + this._tree[path] = {} as Record< + EventType, + Record void> + >; } - if (!this._tree[path][eventType]) { - this._tree[path][eventType] = {}; + if (!this._tree[path]![eventType]) { + this._tree[path]![eventType] = {}; } - this._tree[path][eventType][eventRegistrationKey] = listener; + this._tree[path]![eventType]![eventRegistrationKey] = listener; this._reverseLookup[eventRegistrationKey] = registration; if (once) { - const subscription = SharedEventEmitter.addListener(eventRegistrationKey, event => { - this._onOnceRemoveRegistration(eventRegistrationKey, listener)(event); - subscription.remove(); - }); + const subscription = SharedEventEmitter.addListener( + eventRegistrationKey, + (event: DatabaseDataSnapshot) => { + this._onOnceRemoveRegistration(eventRegistrationKey, listener)(event); + subscription.remove(); + }, + ); } else { - const registration = SharedEventEmitter.addListener(eventRegistrationKey, listener); + const registrationSubscription = SharedEventEmitter.addListener( + eventRegistrationKey, + listener, + ); // add this listener registration info to our emitter-key map // in case we need to identify and remove a specific listener later const registrations = this._allocate(this._registry, eventRegistrationKey); - registrations.add(registration); + registrations.add(registrationSubscription); } return eventRegistrationKey; @@ -326,18 +445,18 @@ class DatabaseSyncTree { * @param registration * @return {boolean} */ - removeRegistration(registration) { + removeRegistration(registration: string): boolean { if (!this._reverseLookup[registration]) { return false; } - const { path, eventType, once } = this._reverseLookup[registration]; + const { path, eventType, once } = this._reverseLookup[registration]!; if (!this._tree[path]) { delete this._reverseLookup[registration]; return false; } - if (!this._tree[path][eventType]) { + if (!this._tree[path]![eventType]) { delete this._reverseLookup[registration]; return false; } @@ -346,10 +465,10 @@ class DatabaseSyncTree { // automatically unsubscribed on native when the first event is sent const registrationObj = this._reverseLookup[registration]; if (registrationObj && !once) { - this.native.off(registrationObj.key, registration); + (this.native as any)?.off(registrationObj.key, registration); } - delete this._tree[path][eventType][registration]; + delete this._tree[path]![eventType]![registration]; delete this._reverseLookup[registration]; return !!registrationObj; @@ -363,7 +482,10 @@ class DatabaseSyncTree { * @return {function(...[*])} * @private */ - _onOnceRemoveRegistration(registration, listener) { + _onOnceRemoveRegistration( + registration: string, + listener: (snapshot: DatabaseDataSnapshot, previousChildName?: string | null) => void, + ): (snapshot: DatabaseDataSnapshot, previousChildName?: string | null) => void { return (...args) => { this.removeRegistration(registration); listener(...args); diff --git a/packages/database/lib/DatabaseThenableReference.js b/packages/database/lib/DatabaseThenableReference.js deleted file mode 100644 index ef7ed55890..0000000000 --- a/packages/database/lib/DatabaseThenableReference.js +++ /dev/null @@ -1,48 +0,0 @@ -/* - * Copyright (c) 2016-present Invertase Limited & Contributors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this library 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 { createDeprecationProxy } from '@react-native-firebase/app/dist/module/common'; -// To avoid React Native require cycle warnings -let DatabaseReference = null; -export function provideReferenceClass(databaseReference) { - DatabaseReference = databaseReference; -} - -export default class DatabaseThenableReference { - constructor(database, path, promise) { - this._ref = createDeprecationProxy(new DatabaseReference(database, path)); - this._promise = promise; - - return new Proxy(this, { - get(target, prop) { - if (prop === 'then' || prop === 'catch') { - return target[prop]; - } - - return target._ref[prop]; - }, - }); - } - - get then() { - return this._promise.then.bind(this._promise); - } - - get catch() { - return this._promise.catch.bind(this._promise); - } -} diff --git a/packages/database/lib/DatabaseThenableReference.ts b/packages/database/lib/DatabaseThenableReference.ts new file mode 100644 index 0000000000..91cc642724 --- /dev/null +++ b/packages/database/lib/DatabaseThenableReference.ts @@ -0,0 +1,60 @@ +/* + * Copyright (c) 2016-present Invertase Limited & Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this library 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 { createDeprecationProxy } from '@react-native-firebase/app/dist/module/common'; +import type DatabaseReference from './DatabaseReference'; + +// To avoid React Native require cycle warnings +let DatabaseReferenceClass: typeof DatabaseReference | null = null; + +export function provideReferenceClass(databaseReference: typeof DatabaseReference): void { + DatabaseReferenceClass = databaseReference; +} + +export default class DatabaseThenableReference { + // @ts-expect-error - _ref is used in Proxy get trap but TypeScript doesn't detect it + private _ref: DatabaseReference; + private _promise: Promise; + + constructor(database: unknown, path: string, promise: Promise) { + if (!DatabaseReferenceClass) { + throw new Error('DatabaseReference class not provided. Call provideReferenceClass first.'); + } + this._ref = createDeprecationProxy( + new DatabaseReferenceClass(database as any, path), + ) as DatabaseReference; + this._promise = promise; + + return new Proxy(this as any, { + get(target, prop) { + if (prop === 'then' || prop === 'catch') { + return target[prop as keyof DatabaseThenableReference]; + } + + return (target._ref as any)[prop]; + }, + }) as DatabaseThenableReference & DatabaseReference; + } + + get then(): Promise['then'] { + return this._promise.then.bind(this._promise); + } + + get catch(): Promise['catch'] { + return this._promise.catch.bind(this._promise); + } +} diff --git a/packages/database/lib/DatabaseTransaction.js b/packages/database/lib/DatabaseTransaction.ts similarity index 64% rename from packages/database/lib/DatabaseTransaction.js rename to packages/database/lib/DatabaseTransaction.ts index 0242de9978..c619c79ad0 100644 --- a/packages/database/lib/DatabaseTransaction.js +++ b/packages/database/lib/DatabaseTransaction.ts @@ -17,6 +17,23 @@ import NativeError from '@react-native-firebase/app/dist/module/internal/NativeFirebaseError'; import { isOther } from '@react-native-firebase/app/dist/module/common'; +import type DatabaseReference from './DatabaseReference'; +import type DatabaseDataSnapshot from './DatabaseDataSnapshot'; +import type { DatabaseInternal } from './types/internal'; + +interface Transaction { + id: number; + reference: DatabaseReference; + transactionUpdater: (currentData: unknown) => unknown; + onComplete: ( + error: Error | null, + committed: boolean, + snapshot: DatabaseDataSnapshot | null, + ) => void; + applyLocally: boolean; + completed: boolean; + started: boolean; +} let transactionId = 0; @@ -25,11 +42,14 @@ let transactionId = 0; * @returns {number} * @private */ - -const generateTransactionId = () => transactionId++; +const generateTransactionId = (): number => transactionId++; export default class DatabaseTransaction { - constructor(database) { + private _database: DatabaseInternal; + private _emitter: DatabaseInternal['emitter']; + private _transactions: Record; + + constructor(database: DatabaseInternal) { this._database = database; this._emitter = database.emitter; this._transactions = {}; @@ -47,7 +67,16 @@ export default class DatabaseTransaction { * @param onComplete * @param applyLocally */ - add(reference, transactionUpdater, onComplete, applyLocally = false) { + add( + reference: DatabaseReference, + transactionUpdater: (currentData: unknown) => unknown, + onComplete: ( + error: Error | null, + committed: boolean, + snapshot: DatabaseDataSnapshot | null, + ) => void, + applyLocally: boolean = false, + ): void { const id = generateTransactionId(); this._transactions[id] = { @@ -74,7 +103,7 @@ export default class DatabaseTransaction { * @return {*} * @private */ - _getTransaction(id) { + _getTransaction(id: number): Transaction | undefined { return this._transactions[id]; } @@ -84,7 +113,7 @@ export default class DatabaseTransaction { * @param id * @private */ - _removeTransaction(id) { + _removeTransaction(id: number): void { setImmediate(() => { delete this._transactions[id]; }); @@ -95,7 +124,16 @@ export default class DatabaseTransaction { * @param event * @private */ - _onTransactionEvent(event) { + _onTransactionEvent(event: { + id: number; + body: { + type: 'update' | 'error' | 'complete'; + value?: unknown; + error?: unknown; + committed?: boolean; + snapshot?: unknown; + }; + }): void { switch (event.body.type) { case 'update': return this._handleUpdate(event); @@ -113,8 +151,13 @@ export default class DatabaseTransaction { * @param event * @private */ - _handleUpdate(event) { - let newValue; + _handleUpdate(event: { + id: number; + body: { + value?: unknown; + }; + }): void { + let newValue: unknown; const { id, body } = event; const { value } = body; @@ -144,7 +187,12 @@ export default class DatabaseTransaction { * @param event * @private */ - _handleError(event) { + _handleError(event: { + id: number; + body: { + error?: unknown; + }; + }): void { const transaction = this._getTransaction(event.id); if (transaction && !transaction.completed) { @@ -152,7 +200,7 @@ export default class DatabaseTransaction { try { // error, committed, snapshot - const error = NativeError.fromEvent(event.body.error, 'database'); + const error = NativeError.fromEvent(event.body.error as any, 'database'); transaction.onComplete(error, false, null); } finally { this._removeTransaction(event.id); @@ -165,7 +213,13 @@ export default class DatabaseTransaction { * @param event * @private */ - _handleComplete(event) { + _handleComplete(event: { + id: number; + body: { + committed?: boolean; + snapshot?: unknown; + }; + }): void { const transaction = this._getTransaction(event.id); if (transaction && !transaction.completed) { @@ -173,7 +227,11 @@ export default class DatabaseTransaction { try { // error, committed, snapshot - transaction.onComplete(null, event.body.committed, Object.assign({}, event.body.snapshot)); + transaction.onComplete( + null, + event.body.committed || false, + Object.assign({}, event.body.snapshot) as DatabaseDataSnapshot | null, + ); } finally { this._removeTransaction(event.id); } diff --git a/packages/database/lib/index.d.ts b/packages/database/lib/index.d.ts deleted file mode 100644 index 780b60db9d..0000000000 --- a/packages/database/lib/index.d.ts +++ /dev/null @@ -1,1327 +0,0 @@ -/* - * Copyright (c) 2016-present Invertase Limited & Contributors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this library 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 { ReactNativeFirebase } from '@react-native-firebase/app'; - -/** - * Firebase Database package for React Native. - * - * #### Example 1 - * - * Access the firebase export from the `database` package: - * - * ```js - * import { firebase } from '@react-native-firebase/database'; - * - * // firebase.database().X - * ``` - * - * #### Example 2 - * - * Using the default export from the `database` package: - * - * ```js - * import database from '@react-native-firebase/database'; - * - * // database().X - * ``` - * - * #### Example 3 - * - * Using the default export from the `app` package: - * - * ```js - * import firebase from '@react-native-firebase/app'; - * import '@react-native-firebase/database'; - * - * // firebase.database().X - * ``` - * - * @firebase database - */ -export namespace FirebaseDatabaseTypes { - import FirebaseModule = ReactNativeFirebase.FirebaseModule; - - /** - * The ServerValue interface provides access to Firebase server values. - */ - export interface ServerValue { - /** - * A placeholder value for auto-populating the current timestamp (time since the Unix epoch, - * in milliseconds) as determined by the Firebase servers. - * - * #### Example - * - * ```js - * firebase.database().ref('sessions').push({ - * startedAt: firebase.database.ServerValue.TIMESTAMP, - * }); - * ``` - */ - TIMESTAMP: object; - - /** - * Returns a placeholder value that can be used to atomically increment the current database value by the provided delta. - * - * #### Example - * - * ```js - * firebase.database().ref('posts/123').update({ - * likes: firebase.database.ServerValue.increment(1), - * }); - * ``` - * - * @param delta The amount to modify the current value atomically. - */ - increment(delta: number): object; - } - - /** - * Realtime Database statics. - * - * #### Example - * - * ```js - * firebase.database; - * ``` - */ - export interface Statics { - /** - * Returns server specific values, such as the server timestamp. - * - * #### Example - * - * ```js - * firebase.database.ServerValue; - * ``` - */ - ServerValue: ServerValue; - SDK_VERSION: string; - } - - export interface TransactionResult { - committed: boolean; - snapshot: DataSnapshot; - } - - /** - * A Reference represents a specific location in your Database and can be used for reading or - * writing data to that Database location. - * - * You can reference the root or child location in your Database by calling `firebase.database().ref()` - * or `firebase.database().ref("child/path")`. - */ - export interface Reference extends Query { - /** - * The parent location of a Reference. The parent of a root Reference is `null`. - * - * #### Example - * - * ```js - * firebase.database().ref().parent; // null - * firebase.database().ref('users/dave').parent; // 'users' reference - * ``` - */ - parent: Reference | null; - - /** - * The root Reference of the Database. - * - * #### Example - * - * ```js - * firebase.database().ref().root; // '/' reference path - * firebase.database().ref('users/ada').root; // '/' reference - * ``` - */ - root: Reference; - - /** - * Gets a Reference for the location at the specified relative path. - * - * The relative path can either be a simple child name (for example, "ada") or a deeper - * slash-separated path (for example, "ada/name/first"). - * - * #### Example - * - * ```js - * const usersRef = firebase.database().ref('users'); - * const adaRef = usersRef.child('ada/name/first'); // childRef path is 'users/ada/name/first' - * ``` - * - * @param path A relative path from this location to the desired child location. - */ - child(path: string): Reference; - - /** - * The last part of the Reference's path. - * For example, "ada" is the key for https://.firebaseio.com/users/ada. - * The key of a root Reference is null. - */ - key: string | null; - - /** - * Writes data to this Database location. - * - * This will overwrite any data at this location and all child locations. - * - * The effect of the write will be visible immediately, and the corresponding events - * ("value", "child_added", etc.) will be triggered. Synchronization of the data to the - * Firebase servers will also be started, and the returned Promise will resolve when - * complete. If provided, the `onComplete` callback will be called asynchronously after - * synchronization has finished. - * - * Passing `null` for the new value is equivalent to calling `remove();` namely, all data at - * this location and all child locations will be deleted. - * - * `set()` will remove any priority stored at this location, so if priority is meant to be - * preserved, you need to use `setWithPriority()` instead. - * - * Note that modifying data with set() will cancel any pending transactions at that location, - * so extreme care should be taken if mixing set() and transaction() to modify the same data. - * - * A single set() will generate a single "value" event at the location where the set() was performed. - * - * #### Example - Setting values - * - * ```js - * const ref = firebase.database().ref('users'); - * - * // Set a single node value - * await ref.child('ada/name/first').set('Ada'); - * await ref.child('ada/name/last').set('Lovelace'); - * - * // Set an object value in a single call - * await ref.child('ada/name').set({ - * first: 'Ada', - * last: 'Lovelace', - * }); - * ``` - * - * #### Example - On complete listener - * - * ```js - * const ref = firebase.database().ref('users'); - * - * await ref.child('ada/first/name').set('Ada', (error) => { - * if (error) console.error(error); - * }); - * ``` - * - * @param value The value to be written (string, number, boolean, object, array, or null). - * @param onComplete Callback called when write to server is complete. Contains the parameters (Error | null). - */ - set(value: any, onComplete?: (error: Error | null) => void): Promise; - - /** - * Writes multiple values to the Database at once. - * - * The `values` argument contains multiple property-value pairs that will be written to the Database - * together. Each child property can either be a simple property (for example, "name") or a - * relative path (for example, "name/first") from the current location to the data to update. - * - * As opposed to the `set()` method, `update()` can be use to selectively update only the referenced - * properties at the current location (instead of replacing all the child properties at the - * current location). - * - * The effect of the write will be visible immediately, and the corresponding events ('value', - * 'child_added', etc.) will be triggered. Synchronization of the data to the Firebase servers - * will also be started, and the returned Promise will resolve when complete. If provided, the - * `onComplete` callback will be called asynchronously after synchronization has finished. - * - * A single update() will generate a single "value" event at the location where the update() - * was performed, regardless of how many children were modified. - * - * Note that modifying data with update() will cancel any pending transactions at that location, - * so extreme care should be taken if mixing update() and transaction() to modify the same data. - * - * Passing `null` to `update()` will remove the data at this location. - * - * #### Example - * - * Modify the 'first' and 'last' properties, but leave other values unchanged at this location. - * - * ```js - * await firebase.database().ref('users/ada/name').update({ - * first: 'Ada', - * last: 'Lovelace', - * }) - * ``` - * - * @param values Object containing multiple values. - * @param onComplete Callback called when write to server is complete. Contains the parameters (Error | null). - */ - update( - values: { [key: string]: any }, - onComplete?: (error: Error | null) => void, - ): Promise; - - /** - * Sets a priority for the data at this Database location. Setting null removes any priority at this location. - * - * See {@link database.Query#orderByPriority} to learn how to use priority values in your query. - * - * #### Example - * - * ```js - * await firebase.database().ref('users/ada').setPriority(1, (error) => { - * if (error) console.error(error); - * }); - * ``` - * - * @param priority The priority value. - * @param onComplete Callback called when write to server is complete. Contains the parameters (Error | null). - */ - setPriority( - priority: string | number | null, - onComplete?: (error: Error | null) => void, - ): Promise; - - /** - * Writes data the Database location. Like `set()` but also specifies the priority for that data. - * - * #### Example - * - * ```js - * await firebase.database().ref('users/ada/name') - * .setWithPriority({ - * first: 'Ada', - * last: 'Lovelace', - * }, 1, (error) => { - * if (error) console.error(error); - * }); - * ``` - * - * @param newVal The new value to set. - * @param newPriority The new priority to set. - * @param onComplete Callback called when write to server is complete. Contains the parameters (Error | null). - */ - setWithPriority( - newVal: any, - newPriority: string | number | null, - onComplete?: (error: Error | null) => void, - ): Promise; - - /** - * Removes the data at this Database location. - * - * Any data at child locations will also be deleted. - * - * The effect of the remove will be visible immediately and the corresponding event 'value' will be triggered. - * Synchronization of the remove to the Firebase servers will also be started, and the returned Promise will - * resolve when complete. If provided, the onComplete callback will be called asynchronously after synchronization - * has finished. - * - * #### Example - * - * ```js - * await firebase.database().ref('users/ada/name') - * .remove(() => { - * console.log('Operation Complete'); - * }); - * ``` - * - * @param onComplete Callback called when write to server is complete. Contains the parameters (Error | null). - */ - remove(onComplete?: (error: Error | null) => void): Promise; - - /** - * Atomically modifies the data at this location. - * - * Atomically modify the data at this location. Unlike a normal `set()`, which just overwrites - * the data regardless of its previous value, `transaction()` is used to modify the existing - * value to a new value, ensuring there are no conflicts with other clients writing to the same - * location at the same time. - * - * To accomplish this, you pass `transaction()` an update function which is used to transform the - * current value into a new value. If another client writes to the location before your new - * value is successfully written, your update function will be called again with the new - * current value, and the write will be retried. This will happen repeatedly until your write - * succeeds without conflict or you abort the transaction by not returning a value from your - * update function. - * - * Note: Modifying data with `set()` will cancel any pending transactions at that location, so - * extreme care should be taken if mixing `set()` and `transaction()` to update the same data. - * - * Note: When using transactions with Security and Firebase Rules in place, be aware that a - * client needs `.read` access in addition to `.write` access in order to perform a transaction. - * This is because the client-side nature of transactions requires the client to read the data - * in order to transactionally update it. - * - * #### Example - * - * ```js - * const userRef = firebase.database().ref('users/ada/profileViews); - * - * userRef.transaction((currentViews) => { - * if (currentViews === null) return 1; - * return currentViews + 1; - * }); - * ``` - * - * @param transactionUpdate A developer-supplied function which will be passed the current data stored at this location (as a JavaScript object). The function should return the new value it would like written (as a JavaScript object). If undefined is returned (i.e. you return with no result) the transaction will be aborted and the data at this location will not be modified. - * @param onComplete A callback function that will be called when the transaction completes. The callback is passed three arguments: a possibly-null Error, a boolean indicating whether the transaction was committed, and a DataSnapshot indicating the final result. If the transaction failed abnormally, the first argument will be an Error object indicating the failure cause. If the transaction finished normally, but no data was committed because no data was returned from transactionUpdate, then second argument will be false. If the transaction completed and committed data to Firebase, the second argument will be true. Regardless, the third argument will be a DataSnapshot containing the resulting data in this location. - * @param applyLocally By default, events are raised each time the transaction update function runs. So if it is run multiple times, you may see intermediate states. You can set this to false to suppress these intermediate states and instead wait until the transaction has completed before events are raised. - */ - transaction( - transactionUpdate: (currentData: any) => any | undefined, - onComplete?: (error: Error | null, committed: boolean, finalResult: DataSnapshot) => void, - applyLocally?: boolean, - ): Promise; - - /** - * Generates a new child location using a unique key and returns its `Reference`. - * - * This is the most common pattern for adding data to a collection of items. - * - * If you provide a value to `push()`, the value will be written to the generated location. - * If you don't pass a value, nothing will be written to the Database and the child will - * remain empty (but you can use the `Reference` elsewhere). - * - * The unique key generated by push() are ordered by the current time, so the resulting list - * of items will be chronologically sorted. The keys are also designed to be unguessable - * (they contain 72 random bits of entropy). - * - * #### Example - * - * ```js - * const newUserRef = firebase.database().ref('users').push(); - * console.log('New record key:', newUserRef.key); - * await newUserRef.set({ - * first: 'Ada', - * last: 'Lovelace', - * }); - * ``` - * - * @param value Optional value to be written at the generated location. - * @param onComplete Callback called when write to server is complete. - */ - push(value?: any, onComplete?: () => void): ThenableReference; - - /** - * Returns an {@link database.OnDisconnect} instance. - * - * #### Example - * - * ```js - * const userDisconnectRef = firebase.database().ref('users/ada/isOnline').onDisconnect(); - * // When going offline - * await userDisconnectRef.update(false); - * ``` - */ - onDisconnect(): OnDisconnect; - } - - export type ThenableReference = Reference; - - /** - * A Query sorts and filters the data at a Database location so only a subset of the child data - * is included. This can be used to order a collection of data by some attribute (for example, - * height of dinosaurs) as well as to restrict a large list of items (for example, chat messages) - * down to a number suitable for synchronizing to the client. Queries are created by chaining - * together one or more of the filter methods defined here. - * - * Just as with a `Reference`, you can receive data from a Query by using the on() method. You will - * only receive events and `DataSnapshot`s for the subset of the data that matches your query. - */ - export interface Query { - /** - * Returns a Reference to the Query's location. - */ - ref: Reference; - - /** - * Creates a Query with the specified ending point. - * - * Using `startAt()`, `endAt()`, and `equalTo()` allows you to choose arbitrary starting and - * ending points for your queries. - * - * The ending point is inclusive, so children with exactly the specified value will be included - * in the query. The optional key argument can be used to further limit the range of the query. - * If it is specified, then children that have exactly the specified value must also have a key - * name less than or equal to the specified key. - * - * You can read more about endAt() in [Filtering data](https://firebase.google.com/docs/database/web/lists-of-data?authuser=0#filtering_data). - * - * #### Example - * - * ```js - * const ref = firebase.database().ref('users'); - * const snapshot = await ref.orderByKey().endAt('Ada Lovelace').once('value'); - * ``` - * - * @param value The value to end at. The argument type depends on which `orderBy*()` function was used in this query. Specify a value that matches the `orderBy*()` type. When used in combination with `orderByKey()`, the value must be a string. - * @param key The child key to end at, among the children with the previously specified priority. This argument is only allowed if ordering by child, value, or priority. - */ - endAt(value: number | string | boolean | null, key?: string): Query; - - /** - * Creates a Query with the specified ending point. - * - * Using `startAt()`, `endAt()`, and `equalTo()` allows you to choose arbitrary starting and - * ending points for your queries. - * - * The optional key argument can be used to further limit the range of the query. If it is - * specified, then children that have exactly the specified value must also have exactly the - * specified key as their key name. This can be used to filter result sets with many matches for the same value. - * - * You can read more about equalTo() in [Filtering data](https://firebase.google.com/docs/database/web/lists-of-data?authuser=0#filtering_data). - * - * #### Example - * - * ```js - * const ref = firebase.database().ref('users'); - * const snapshot = await ref.orderByChild('age').equalTo(30).once('value'); - * ``` - * - * @param value The value to match for. The argument type depends on which `orderBy*()` function was used in this query. Specify a value that matches the `orderBy*()` type. When used in combination with `orderByKey()`, the value must be a string. - * @param key The child key to start at, among the children with the previously specified priority. This argument is only allowed if ordering by child, value, or priority. - */ - equalTo(value: number | string | boolean | null, key?: string): Query; - - /** - * Returns whether or not the current and provided queries represent the same location, have the same query parameters. - * - * Two Reference objects are equivalent if they represent the same location and are from the same instance of - * {@link app}. Equivalent queries share the same sort order, limits, and starting and ending points. - * - * #### Example - * - * ```js - * const ref1 = firebase.database().ref('users').orderByKey().endAt('Ada Lovelace'); - * const ref2 = firebase.database().ref('users').orderByKey(); - * - * console.log(ref1.isEqual(ref2)); // false - * ``` - * - * #### Example - * - * ```js - * const ref1 = firebase.database().ref('users').orderByKey().endAt('Ada Lovelace'); - * const ref2 = firebase.database().ref('users').endAt('Ada Lovelace').orderByKey(); - * - * console.log(ref1.isEqual(ref2)); // true - * ``` - * - * @param other The query to compare against. - */ - isEqual(other: Query): boolean; - - /** - * Generates a new `Query` limited to the first specific number of children. - * - * The `limitToFirst()` method is used to set a maximum number of children to be synced for a - * given callback. If we set a limit of 100, we will initially only receive up to 100 `child_added` - * events. If we have fewer than 100 messages stored in our Database, a child_added event will - * fire for each message. However, if we have over 100 messages, we will only receive a `child_added` - * event for the first 100 ordered messages. As items change, we will receive `child_removed` events - * for each item that drops out of the active list so that the total number stays at 100. - * - * You can read more about `limitToFirst()` in [Filtering data](https://firebase.google.com/docs/database/web/lists-of-data?authuser=0#filtering_data). - * - * #### Example - * - * ```js - * const snapshot = firebase.database().ref('users').orderByKey().limitToFirst(2).once('value'); - * console.log(snapshot.numChildren()); // 2 - * ``` - * - * @param limit The maximum number of nodes to include in this query. - */ - limitToFirst(limit: number): Query; - - /** - * Generates a new `Query` object limited to the last specific number of children. - * - * The `limitToLast()` method is used to set a maximum number of children to be synced for a given - * callback. If we set a limit of 100, we will initially only receive up to 100 `child_added` events. - * If we have fewer than 100 messages stored in our Database, a `child_added` event will fire for - * each message. However, if we have over 100 messages, we will only receive a `child_added` event - * for the last 100 ordered messages. As items change, we will receive `child_removed` events for - * each item that drops out of the active list so that the total number stays at 100. - * - * You can read more about `limitToLast()` in [Filtering data](https://firebase.google.com/docs/database/web/lists-of-data?authuser=0#filtering_data). - * - * #### Example - * - * ```js - * const snapshot = firebase.database().ref('users').orderByKey().limitToLast(2).once('value'); - * console.log(snapshot.numChildren()); // 2 - * ``` - * - * @param limit The maximum number of nodes to include in this query. - */ - limitToLast(limit: number): Query; - - /** - * Detaches a callback previously attached with `on()`. - * - * Detach a callback previously attached with `on()`. Note that if `on()` was called multiple times - * with the same eventType and callback, the callback will be called multiple times for each - * event, and `off()` must be called multiple times to remove the callback. Calling `off()` on a parent - * listener will not automatically remove listeners registered on child nodes, `off()` must also be - * called on any child listeners to remove the callback. - * - * If a callback is not specified, all callbacks for the specified eventType will be removed. - * Similarly, if no eventType is specified, all callbacks for the `Reference` will be removed. - * - * #### Example - * - * ```js - * const ref = firebase.database().ref('settings'); - * const onValueChange = function(snapshot) { ... }; - * const onChildAdded = function(snapshot) { ... }; - * - * ref.on('value', onValueChange); - * ref.child('meta-data').on('child_added', onChildAdded); - * // Sometime later... - * ref.off('value', onValueChange); - * ref.child('meta-data').off('child_added', onChildAdded); - * ``` - * - * @param eventType One of the following strings: "value", "child_added", "child_changed", "child_removed", or "child_moved." If omitted, all callbacks for the Reference will be removed. - * @param callback The callback function that was passed to `on()` or `undefined` to remove all callbacks. - * @param context The context that was passed to `on()`. - */ - off( - eventType?: EventType, - callback?: (a: DataSnapshot, b?: string | null) => void, - context?: Record, - ): void; - - /** - * Listens for data changes at a particular location. - * - * This is the primary way to read data from a Database. Your callback will be triggered for the - * initial data and again whenever the data changes. Use `off()` to stop receiving updates.. - * - * **value** event - * - * This event will trigger once with the initial data stored at this location, and then trigger - * again each time the data changes. The `DataSnapshot` passed to the callback will be for the location - * at which on() was called. It won't trigger until the entire contents has been synchronized. - * If the location has no data, it will be triggered with an empty `DataSnapshot` - * (`val()` will return `null`). - * - * **child_added** event - * - * This event will be triggered once for each initial child at this location, and it will be - * triggered again every time a new child is added. The `DataSnapshot` passed into the callback - * will reflect the data for the relevant child. For ordering purposes, it is passed a second argument - * which is a string containing the key of the previous sibling child by sort order, or `null` if - * it is the first child. - * - * **child_removed** event - * - * This event will be triggered once every time a child is removed. The `DataSnapshot` passed into - * the callback will be the old data for the child that was removed. A child will get removed when either: - * - a client explicitly calls `remove()` on that child or one of its ancestors - * - a client calls `set(null)` on that child or one of its ancestors - * - that child has all of its children removed - * - there is a query in effect which now filters out the child (because it's sort order changed or the max limit was hit) - * - * **child_changed** event - * - * This event will be triggered when the data stored in a child (or any of its descendants) changes. - * Note that a single `child_changed` event may represent multiple changes to the child. The - * `DataSnapshot` passed to the callback will contain the new child contents. For ordering purposes, - * the callback is also passed a second argument which is a string containing the key of the previous - * sibling child by sort order, or `null` if it is the first child. - * - * **child_moved** event - * - * This event will be triggered when a child's sort order changes such that its position relative - * to its siblings changes. The `DataSnapshot` passed to the callback will be for the data of the child - * that has moved. It is also passed a second argument which is a string containing the key of the - * previous sibling child by sort order, or `null` if it is the first child. - * - * @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. For ordering purposes, "child_added", "child_changed", and "child_moved" will also be passed a string containing the key of the previous child, by sort order, or `null` if it is the first child. - * @param cancelCallbackOrContext An optional callback that will be notified if your event subscription is ever canceled because your client does not have permission to read this data (or it had permission but has now lost it). This callback will be passed an `Error` object indicating why the failure occurred. - * @param context If provided, this object will be used as `this` when calling your callback(s). - * - */ - on( - eventType: EventType, - callback: (data: DataSnapshot, previousChildKey?: string | null) => void, - cancelCallbackOrContext?: ((a: Error) => void) | Record | null, - context?: Record | null, - ): (a: DataSnapshot | null, b?: string | null) => void; - - /** - * Listens for exactly one event of the specified event type, and then stops listening. - * - * This is equivalent to calling `on()`, and then calling `off()` inside the callback function. See `on()` for details on the event types. - * - * #### Example - * - * ```js - * // Promise - * const snapshot = await firebase.database().ref('users').once('value'); - * // Callback - * firebase.database().ref('users).once('value', (snapshot) => { - * console.log(snapshot.val()); - * }); - * ``` - * - * @param eventType One of the following strings: "value", "child_added", "child_changed", "child_removed", or "child_moved." - * @param successCallback A callback that fires when the specified event occurs. The callback will be passed a DataSnapshot. For ordering purposes, "child_added", "child_changed", and "child_moved" will also be passed a string containing the key of the previous child by sort order, or `null` if it is the first child. - @param failureCallbackContext An optional callback that will be notified if your client does not have permission to read the data. This callback will be passed an Error object indicating why the failure occurred. - */ - - once( - eventType: EventType, - successCallback?: (a: DataSnapshot, b?: string | null) => any, - failureCallbackContext?: ((a: Error) => void) | Record | null, - ): Promise; - - /** - * 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. - * - * Firebase queries allow you to order your data by any child key on the fly. However, if you know in advance what - * your indexes will be, you can define them via the [.indexOn](https://firebase.google.com/docs/database/security/indexing-data?authuser=0) - * rule in your Security Rules for better performance. - * - * You can read more about orderByChild() in [Sort data](https://firebase.google.com/docs/database/web/lists-of-data?authuser=0#sort_data). - * - * #### Example - * - * ```js - * const snapshot = await firebase.database().ref('users').orderByChild('age').once('value'); - * snapshot.forEach((snapshot) => { - * console.log('Users age:', snapshot.val().age); - * }); - * ``` - * - * @param path The child path node to order by. - */ - orderByChild(path: string): Query; - - /** - * Generates a new `Query` object ordered by key. - * - * Sorts the results of a query by their (ascending) key values. - * - * You can read more about `orderByKey()` in [Sort data](https://firebase.google.com/docs/database/web/lists-of-data?authuser=0#sort_data). - * - * #### Example - * - * ```js - * const snapshot = await firebase.database().ref('users').orderByKey().once('value'); - * snapshot.forEach((snapshot) => { - * console.log('User:', snapshot.val()); - * }); - * ``` - */ - orderByKey(): Query; - - /** - * Generates a new Query object ordered by priority. - * - * Applications need not use priority but can order collections by ordinary properties - * (see [Sort data](https://firebase.google.com/docs/database/web/lists-of-data?authuser=0#sort_data) - * for alternatives to priority). - */ - orderByPriority(): Query; - - /** - * 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. - * - * You can read more about `orderByValue()` in [Sort data](https://firebase.google.com/docs/database/web/lists-of-data?authuser=0#sort_data). - * - * #### Example - * - * ```js - * await firebase.database().ref('scores').orderByValue().once('value'); - * ``` - */ - orderByValue(): Query; - - /** - * Creates a `Query` with the specified starting point. - * - * Using `startAt()`, `endAt()`, and `equalTo()` allows you to choose arbitrary starting and - * ending points for your queries. - * - * The starting point is inclusive, so children with exactly the specified value will be included - * in the query. The optional key argument can be used to further limit the range of the query. - * If it is specified, then children that have exactly the specified value must also have a key - * name greater than or equal to the specified key. - * - * You can read more about `startAt()` in [Filtering data](https://firebase.google.com/docs/database/web/lists-of-data?authuser=0#filtering_data). - * - * #### Example - * - * ```js - * await firebase.database().ref('users').orderByChild('age').startAt(21).once('value'); - * ``` - * - * @param value The value to start at. The argument type depends on which `orderBy*()` function was used in this query. Specify a value that matches the `orderBy*()` type. When used in combination with `orderByKey()`, the value must be a string. - * @param key The child key to start at. This argument is only allowed if ordering by child, value, or priority. - */ - startAt(value: number | string | boolean | null, key?: string): Query; - - /** - * Returns a JSON-serializable representation of this object. - */ - toJSON(): object; - - /** - * Gets the absolute URL for this location. - * - * The `toString()` method returns a URL that is ready to be put into a browser, curl command, or - * a `firebase.database().refFromURL()` call. Since all of those expect the URL to be url-encoded, - * `toString()` returns an encoded URL. - * - * Append '.json' to the returned URL when typed into a browser to download JSON-formatted data. - * If the location is secured (that is, not publicly readable), you will get a permission-denied error. - * - * #### Example - * - * ```js - * const ref1 = firebase.database().ref(); - * const ref2 = firebase.database().ref('users').orderByValue(); - * - * ref1.toString(); // https://sample-app.firebaseio.com/ - * ref2.toString(); // https://sample-app.firebaseio.com/users - * ``` - */ - toString(): string; - - /** - * By calling `keepSynced(true)` on a location, the data for that location will automatically - * be downloaded and kept in sync, even when no listeners are attached for that location. - * - * #### Example - * - * ```js - * const ref = firebase.database().ref('users'); - * await ref.keepSynced(true); - * ``` - * - * @param bool Pass `true` to keep this location synchronized, pass `false` to stop synchronization. - */ - keepSynced(bool: boolean): Promise; - } - - /** - * The `onDisconnect` class allows you to write or clear data when your client disconnects from the Database server. - * These updates occur whether your client disconnects cleanly or not, so you can rely on them to clean up data - * even if a connection is dropped or a client crashes. - * - * The onDisconnect class is most commonly used to manage presence in applications where it is - * useful to detect how many clients are connected and when other clients disconnect. - * - * To avoid problems when a connection is dropped before the requests can be transferred to the Database - * server, these functions should be called before writing any data. - * - * Note that `onDisconnect` operations are only triggered once. If you want an operation to occur each time a - * disconnect occurs, you'll need to re-establish the `onDisconnect` operations each time you reconnect. - */ - export interface OnDisconnect { - /** - * Cancels all previously queued `onDisconnect()` set or update events for this location and all children. - * - * If a write has been queued for this location via a `set()` or `update()` at a parent location, - * the write at this location will be canceled, though writes to sibling locations will still occur. - * - * #### Example - * - * ```js - * const ref = firebase.database().ref('onlineState'); - * await ref.onDisconnect().set(false); - * // Sometime later... - * await ref.onDisconnect().cancel(); - * ``` - * - * @param onComplete An optional callback function that will be called when synchronization to the server has completed. The callback will be passed a single parameter: null for success, or an Error object indicating a failure. - */ - cancel(onComplete?: (error: Error | null) => void): Promise; - - /** - * Ensures the data at this location is deleted when the client is disconnected (due to closing the browser, navigating to a new page, or network issues). - * - * @param onComplete An optional callback function that will be called when synchronization to the server has completed. The callback will be passed a single parameter: null for success, or an Error object indicating a failure. - */ - remove(onComplete?: (error: Error | null) => void): Promise; - - /** - * Ensures the data at this location is set to the specified value when the client is disconnected - * (due to closing the app, navigating to a new view, or network issues). - * - * `set()` is especially useful for implementing "presence" systems, where a value should be changed - * or cleared when a user disconnects so that they appear "offline" to other users. - * - * Note that `onDisconnect` operations are only triggered once. If you want an operation to occur each time a - * disconnect occurs, you'll need to re-establish the `onDisconnect` operations each time. - * - * #### Example - * - * ```js - * var ref = firebase.database().ref('users/ada/status'); - * await ref.onDisconnect().set('I disconnected!'); - * ``` - * - * @param value The value to be written to this location on disconnect (can be an object, array, string, number, boolean, or null). - * @param onComplete An optional callback function that will be called when synchronization to the Database server has completed. The callback will be passed a single parameter: null for success, or an Error object indicating a failure. - */ - set(value: any, onComplete?: (error: Error | null) => void): Promise; - - /** - * Ensures the data at this location is set to the specified value and priority when the client is disconnected (due to closing the browser, navigating to a new page, or network issues). - * - * @param value The value to set. - * @param priority The priority to set - * @param onComplete An optional callback function that will be called when synchronization to the Database server has completed. The callback will be passed a single parameter: null for success, or an Error object indicating a failure. - */ - setWithPriority( - value: any, - priority: string | number | null, - onComplete?: (error: Error | null) => void, - ): Promise; - - /** - * Writes multiple values at this location when the client is disconnected (due to closing the browser, navigating to a new page, or network issues). - * - * The `values` argument contains multiple property-value pairs that will be written to the Database together. - * Each child property can either be a simple property (for example, "name") or a relative path (for example, - * "name/first") from the current location to the data to update. - * - * As opposed to the `set()` method, `update()` can be use to selectively update only the referenced - * properties at the current location (instead of replacing all the child properties at the current location). - * - * #### Example - * - * ```js - * var ref = firebase.database().ref("users/ada"); - * ref.update({ - * onlineState: true, - * status: "I'm online." - * }); - * ref.onDisconnect().update({ - * onlineState: false, - * status: "I'm offline." - * }); - * ``` - * - * @param values Object containing multiple values. - * @param onComplete An optional callback function that will be called when synchronization to the server has completed. The callback will be passed a single parameter: null for success, or an Error object indicating a failure. - */ - update( - values: { [key: string]: any }, - onComplete?: (error: Error | null) => void, - ): Promise; - } - - export type EventType = - | 'value' - | 'child_added' - | 'child_changed' - | 'child_moved' - | 'child_removed'; - - /** - * A `DataSnapshot` contains data from a Database location. - * - * Any time you read data from the Database, you receive the data as a `DataSnapshot`. A `DataSnapshot` - * is passed to the event callbacks you attach with `on()` or `once()`. You can extract the contents - * of the snapshot as a JavaScript object by calling the val() method. Alternatively, you can traverse - * into the snapshot by calling `child()` to return child snapshots (which you could then call `val()` on). - */ - export interface DataSnapshot { - /** - * The key (last part of the path) of the location of this `DataSnapshot`. - * - * The last token in a Database location is considered its key. For example, "ada" is the key - * for the /users/ada/ node. Accessing the key on any `DataSnapshot` will return the key for the - * location that generated it. However, accessing the key on the root URL of a Database will return `null`. - */ - key: string | null; - - /** - * The Reference for the location that generated this `DataSnapshot`. - */ - ref: Reference; - - /** - * Gets another `DataSnapshot` for the location at the specified relative path. - * - * Passing a relative path to the `child()` method of a DataSnapshot returns another `DataSnapshot` - * for the location at the specified relative path. The relative path can either be a simple child - * name (for example, "ada") or a deeper, slash-separated path (for example, "ada/name/first"). - * If the child location has no data, an empty `DataSnapshot` (that is, a `DataSnapshot` whose value - * is `null`) is returned. - * - * #### Example - * - * ```js - * const snapshot = await firebase.database().ref('users/ada').once('value'); - * snapshot.child('name').val(); // {first:"Ada",last:"Lovelace"} - * snapshot.child('name/first').val(); // "Ada" - * snapshot.child('name/foo').val(); // null - * ``` - * - * @param path A relative path to the location of child data. - */ - child(path: string): DataSnapshot; - - /** - * Returns true if this `DataSnapshot` contains any data. It is slightly more efficient than using snapshot.val() !== null. - * - * #### Example - * - * ```js - * const snapshot = await firebase.database().ref('users/ada').once('value'); - * snapshot.exists(); // true - * snapshot.child('name/foo').exists(); // false - * ``` - */ - exists(): boolean; - - /** - * Exports the entire contents of the DataSnapshot as a JavaScript object. - * - * The `exportVal()` method is similar to val(), except priority information is included (if available), - * making it suitable for backing up your data. - * - * #### Example - * - * ```js - * const snapshot = await firebase.database().ref('users/ada').once('value'); - * const data = snapshot.exportVal(); - * console.log(data['.value']); // { ... } - * console.log(data['.priority']); // null - * ``` - */ - exportVal(): any; - - /** - * Enumerates the top-level children in the `DataSnapshot`. - * - * Because of the way JavaScript objects work, the ordering of data in the JavaScript object - * returned by `val()` is not guaranteed to match the ordering on the server nor the ordering - * of `child_added` events. That is where `forEach()` comes in handy. It guarantees the children of - * a DataSnapshot will be iterated in their query order. - * - * If no explicit `orderBy*()` method is used, results are returned ordered by key (unless priorities are used, in which case, results are returned by priority). - * - * @param action A function that will be called for each child DataSnapshot. The callback can return true to cancel further enumeration. - */ - forEach(action: (child: DataSnapshot) => true | undefined): boolean; - - /** - * Gets the priority value of the data in this DataSnapshot. - */ - getPriority(): string | number | null; - - /** - * Returns true if the specified child path has (non-null) data. - * - * #### Example - * - * ```js - * const snapshot = await firebase.database().ref('users/ada').once('value'); - * console.log(snapshot.hasChild('name')); // true - * console.log(snapshot.hasChild('foo')); // false - * ``` - * - * @param path A relative path to the location of a potential child. - */ - hasChild(path: string): boolean; - - /** - * Returns whether or not the `DataSnapshot` has any non-null child properties. - * - * You can use `hasChildren()` to determine if a `DataSnapshot` has any children. If it does, you - * can enumerate them using `forEach()`. If it doesn't, then either this snapshot contains a primitive - * value (which can be retrieved with `val()`) or it is empty (in which case, `val()` will return null). - * - * #### Example - * - * ```js - * const snapshot = await firebase.database().ref('users').once('value'); - * console.log(snapshot.hasChildren()); // true - * ``` - */ - hasChildren(): boolean; - - /** - * Returns the number of child properties of this DataSnapshot. - */ - numChildren(): number; - - /** - * Returns a JSON-serializable representation of this object. - */ - toJSON(): object | null; - - /** - * Extracts a JavaScript value from a `DataSnapshot`. - * - * Depending on the data in a DataSnapshot, the `val()` method may return a scalar type (string, - * number, or boolean), an array, or an object. It may also return null, indicating that the - * `DataSnapshot` is empty (contains no data). - * - * #### Example - * - * ```js - * const snapshot = await firebase.database().ref('users/ada/last').once('value'); - * snapshot.val(); // "Lovelace" - * ``` - */ - val(): any; - } - - /** - * - * The Firebase Database service is available for the default app or a given app. - * - * #### Example 1 - * - * Get the database instance for the **default app**: - * - * ```js - * const databaseForDefaultApp = firebase.database(); - * ``` - * - * #### Example 2 - * - * Get the database instance for a **secondary app**: - * - * ```js - * const otherApp = firebase.app('otherApp'); - * const databaseForOtherApp = firebase.database(otherApp); - * ``` - * - */ - export class Module extends FirebaseModule { - /** - * The current `FirebaseApp` instance for this Firebase service. - */ - app: ReactNativeFirebase.FirebaseApp; - - /** - * Returns the current Firebase Database server time as a JavaScript Date object. - */ - getServerTime(): Date; - - /** - * Returns a `Reference` representing the location in the Database corresponding to the provided path. - * If no path is provided, the Reference will point to the root of the Database. - * - * #### Example - * - * ```js - * // Get a reference to the root of the Database - * const rootRef = firebase.database().ref(); - * - * // Get a reference to the /users/ada node - * const adaRef = firebase.database().ref("users/ada"); - * ``` - * - * @param path Optional path representing the location the returned `Reference` will point. If not provided, the returned `Reference` will point to the root of the Database. - */ - ref(path?: string): Reference; - - /** - * Returns a `Reference` representing the location in the Database corresponding to the provided Firebase URL. - * - * An exception is thrown if the URL is not a valid Firebase Database URL or it has a different domain than the current Database instance. - * - * Note that all query parameters (orderBy, limitToLast, etc.) are ignored and are not applied to the returned Reference. - * - * #### Example - * - * ```js - * // Get a reference to the root of the Database - * const rootRef = firebase.database().ref("https://.firebaseio.com"); - * ``` - * - * @param url The Firebase URL at which the returned Reference will point. - */ - refFromURL(url: string): Reference; - - /** - * Reconnects to the server and synchronizes the offline Database state with the server state. - * - * This method should be used after disabling the active connection with `goOffline()`. Once - * reconnected, the client will transmit the proper data and fire the appropriate events so that - * your client "catches up" automatically. - * - * #### Example - * - * ```js - * await firebase.database().goOnline(); - * ``` - */ - goOnline(): Promise; - - /** - * Disconnects from the server (all Database operations will be completed offline). - * - * The client automatically maintains a persistent connection to the Database server, which - * will remain active indefinitely and reconnect when disconnected. However, the `goOffline()` and - * `goOnline()` methods may be used to control the client connection in cases where a persistent - * connection is undesirable. - * - * While offline, the client will no longer receive data updates from the Database. However, - * all Database operations performed locally will continue to immediately fire events, allowing - * your application to continue behaving normally. Additionally, each operation performed locally - * will automatically be queued and retried upon reconnection to the Database server. - * - * To reconnect to the Database and begin receiving remote events, see `goOnline()`. - * - * #### Example - * - * ```js - * await firebase.database().goOnline(); - * ``` - */ - goOffline(): Promise; - - /** - * Sets whether persistence is enabled for all database calls for the current app - * instance. - * - * > Ensure this is called before any database calls are performed, otherwise - * persistence will only come into effect when the app is next started. - * - * #### Example - * - * ```js - * firebase.database().setPersistenceEnabled(true); - * - * async function bootstrap() { - * // Bootstrapping application - * const snapshot = await firebase.database().ref('settings').once('value'); - * } - * ``` - * - * @param enabled Whether persistence is enabled for the Database service. - */ - setPersistenceEnabled(enabled: boolean): void; - - /** - * Sets the native logging level for the database module. By default, - * only warnings and errors are logged natively. Setting this to true will log all - * database events. - * - * > Ensure logging is disabled for production apps, as excessive logging can cause performance issues. - * - * #### Example - * - * ```js - * // Set debug logging if developing - * if (__DEV__) { - * firebase.database().setLoggingEnabled(true); - * } - * ``` - * - * @param enabled Whether debug logging is enabled. - */ - setLoggingEnabled(enabled: boolean): void; - - /** - * By default Firebase Database will use up to 10MB of disk space to cache data. If the cache grows beyond this size, - * Firebase Database will start removing data that hasn't been recently used. If you find that your application - * caches too little or too much data, call this method to change the cache size. This method must be called before - * creating your first Database reference and only needs to be called once per application. - * - * Note that the specified cache size is only an approximation and the size on disk may temporarily exceed it at times. - * Cache sizes smaller than 1 MB or greater than 100 MB are not supported. - * - * #### Example - * - * ```js - * firebase.database().setPersistenceEnabled(true); - * firebase.database().setPersistenceCacheSizeBytes(2000000); // 2MB - * - * async function bootstrap() { - * // Bootstrapping application - * const snapshot = await firebase.database().ref('settings').once('value'); - * } - * ``` - * - * @param bytes The new size of the cache in bytes. - */ - setPersistenceCacheSizeBytes(bytes: number): void; - - /** - * Modify this Database instance to communicate with the Firebase Database emulator. - * This must be called synchronously immediately following the first call to firebase.database(). - * Do not use with production credentials as emulator traffic is not encrypted. - * - * Note: on android, hosts 'localhost' and '127.0.0.1' are automatically remapped to '10.0.2.2' (the - * "host" computer IP address for android emulators) to make the standard development experience easy. - * If you want to use the emulator on a real android device, you will need to specify the actual host - * computer IP address. - * - * @param host: emulator host (eg, 'localhost') - * @param port: emulator port (eg, 9000) - */ - useEmulator(host: string, port: number): void; - } -} - -type DatabaseNamespace = ReactNativeFirebase.FirebaseModuleWithStaticsAndApp< - FirebaseDatabaseTypes.Module, - FirebaseDatabaseTypes.Statics -> & { - database: ReactNativeFirebase.FirebaseModuleWithStaticsAndApp< - FirebaseDatabaseTypes.Module, - FirebaseDatabaseTypes.Statics - >; - firebase: ReactNativeFirebase.Module; - app(name?: string): ReactNativeFirebase.FirebaseApp; -}; - -declare const defaultExport: DatabaseNamespace; - -export const firebase: ReactNativeFirebase.Module & { - database: typeof defaultExport; - app( - name?: string, - ): ReactNativeFirebase.FirebaseApp & { database(): FirebaseDatabaseTypes.Module }; -}; - -export * from './modular'; - -export default defaultExport; - -/** - * Attach namespace to `firebase.` and `FirebaseApp.`. - */ -declare module '@react-native-firebase/app' { - namespace ReactNativeFirebase { - import FirebaseModuleWithStaticsAndApp = ReactNativeFirebase.FirebaseModuleWithStaticsAndApp; - interface Module { - database: FirebaseModuleWithStaticsAndApp< - FirebaseDatabaseTypes.Module, - FirebaseDatabaseTypes.Statics - >; - } - - interface FirebaseApp { - database(databaseUrl?: string): FirebaseDatabaseTypes.Module; - } - } -} diff --git a/packages/database/lib/index.ts b/packages/database/lib/index.ts new file mode 100644 index 0000000000..98e7336b05 --- /dev/null +++ b/packages/database/lib/index.ts @@ -0,0 +1,56 @@ +/* + * Copyright (c) 2016-present Invertase Limited & Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this library 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 type { FirebaseDatabaseTypes } from './types/namespaced'; + +// Public/modular types (ServerValue not re-exported as type so value from modular is used) +export type { + Database, + DataSnapshot, + EventType, + OnDisconnect, + Query, + Reference, + Statics, + ThenableReference, + TransactionResult, +} from './types/database'; + +// Namespaced (deprecated) types +export type { FirebaseDatabaseTypes } from './types/namespaced'; + +// Modular API +export * from './modular'; + +// Namespaced default export and firebase +export { default, firebase } from './namespaced'; + +declare module '@react-native-firebase/app' { + // eslint-disable-next-line @typescript-eslint/no-namespace -- module augmentation uses namespace + namespace ReactNativeFirebase { + interface Module { + database: ReactNativeFirebase.FirebaseModuleWithStaticsAndApp< + FirebaseDatabaseTypes.Module, + FirebaseDatabaseTypes.Statics + >; + } + + interface FirebaseApp { + database(databaseUrl?: string): FirebaseDatabaseTypes.Module; + } + } +} diff --git a/packages/database/lib/modular.ts b/packages/database/lib/modular.ts new file mode 100644 index 0000000000..db671dc97f --- /dev/null +++ b/packages/database/lib/modular.ts @@ -0,0 +1,210 @@ +/* + * Copyright (c) 2016-present Invertase Limited & Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this library 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 { getApp } from '@react-native-firebase/app'; +import { MODULAR_DEPRECATION_ARG } from '@react-native-firebase/app/dist/module/common'; +import type { FirebaseApp } from '@react-native-firebase/app'; +import type { Database, Reference } from './types/database'; +import type { DatabaseInternal } from './types/internal'; +import DatabaseStatics from './DatabaseStatics'; + +const { ServerValue } = DatabaseStatics; + +type WithModularDeprecationArg = F extends (...args: infer P) => infer R + ? (...args: [...P, typeof MODULAR_DEPRECATION_ARG]) => R + : never; + +/** + * Returns the instance of the Realtime Database SDK that is associated with + * the provided FirebaseApp. Initializes a new instance with + * default settings if no instance exists or if the existing + * instance uses a custom database URL. + * + * @param app - The FirebaseApp instance that the returned Realtime Database instance is associated with. Optional. + * @param url - Database URL. Optional. + * @returns {Database} + */ +export function getDatabase(app?: FirebaseApp, url?: string): Database { + if (app) { + return getApp(app.name).database(url); + } + + return getApp().database(url); +} + +/** + * Modify this Database instance to communicate with the Firebase Database emulator. + * @param db - Database instance. + * @param host - emulator host (e.g. - 'localhost') + * @param port - emulator port (e.g. - 9000) + * @returns {void} + */ +export function connectDatabaseEmulator(db: Database, host: string, port: number): void { + return ( + (db as DatabaseInternal).useEmulator as WithModularDeprecationArg< + DatabaseInternal['useEmulator'] + > + ).call(db, host, port, MODULAR_DEPRECATION_ARG); +} + +/** + * Disconnects from the server (all Database operations will be completed offline). + * @param db - Database instance. + * @returns {Promise} + */ +export function goOffline(db: Database): Promise { + return ( + (db as DatabaseInternal).goOffline as WithModularDeprecationArg + ).call(db, MODULAR_DEPRECATION_ARG); +} + +/** + * Reconnects to the server and synchronizes the offline Database state with the server state. + * @param db - Database instance. + * @returns {Promise} + */ +export function goOnline(db: Database): Promise { + return ( + (db as DatabaseInternal).goOnline as WithModularDeprecationArg + ).call(db, MODULAR_DEPRECATION_ARG); +} + +/** + * Returns a Reference representing the location in the Database corresponding to the provided path. + * @param db - Database instance. + * @param path - Optional path representing the location the returned Reference will point. + * @returns {Reference} + */ +export function ref(db: Database, path?: string): Reference { + return ((db as DatabaseInternal).ref as WithModularDeprecationArg).call( + db, + path, + MODULAR_DEPRECATION_ARG, + ); +} + +/** + * Generates a Reference from a database URL. + * @param db - Database instance. + * @param url - The Firebase URL at which the returned Reference will point. + * @returns {Reference} + */ +export function refFromURL(db: Database, url: string): Reference { + return ( + (db as DatabaseInternal).refFromURL as WithModularDeprecationArg + ).call(db, url, MODULAR_DEPRECATION_ARG); +} + +/** + * Sets whether persistence is enabled for all database calls for the current app instance. + * @param db - Database instance. + * @param enabled - Whether persistence is enabled for the Database service. + * @returns {void} + */ +export function setPersistenceEnabled(db: Database, enabled: boolean): void { + return ( + (db as DatabaseInternal).setPersistenceEnabled as WithModularDeprecationArg< + DatabaseInternal['setPersistenceEnabled'] + > + ).call(db, enabled, MODULAR_DEPRECATION_ARG); +} + +/** + * Sets the native logging level for the database module. + * @param db - Database instance. + * @param enabled - Whether debug logging is enabled. + * @returns {void} + */ +export function setLoggingEnabled(db: Database, enabled: boolean): void { + return ( + (db as DatabaseInternal).setLoggingEnabled as WithModularDeprecationArg< + DatabaseInternal['setLoggingEnabled'] + > + ).call(db, enabled, MODULAR_DEPRECATION_ARG); +} + +/** + * Sets the cache size for persistence. + * @param db - Database instance. + * @param bytes - The new size of the cache in bytes. + * @returns {void} + */ +export function setPersistenceCacheSizeBytes(db: Database, bytes: number): void { + return ( + (db as DatabaseInternal).setPersistenceCacheSizeBytes as WithModularDeprecationArg< + DatabaseInternal['setPersistenceCacheSizeBytes'] + > + ).call(db, bytes, MODULAR_DEPRECATION_ARG); +} + +/** + * Force the use of longPolling instead of websockets. + */ +export function forceLongPolling(): void { + throw new Error('forceLongPolling() is not implemented'); +} + +/** + * Force the use of websockets instead of longPolling. + */ +export function forceWebSockets(): void { + throw new Error('forceWebSockets() is not implemented'); +} + +/** + * Returns the current Firebase Database server time as a JavaScript Date object. + * @param db - Database instance. + * @returns {Date} + */ +export function getServerTime(db: Database): Date { + return ( + (db as DatabaseInternal).getServerTime as WithModularDeprecationArg< + DatabaseInternal['getServerTime'] + > + ).call(db, MODULAR_DEPRECATION_ARG); +} + +/** + * Returns a placeholder value for auto-populating the current timestamp. + * @returns {object} + */ +export function serverTimestamp(): object { + return ServerValue.TIMESTAMP; +} + +/** + * Returns a placeholder value that can be used to atomically increment the current database value. + * @param delta - The amount to modify the current value atomically. + * @returns {object} + */ +export function increment(delta: number): object { + return (ServerValue.increment as any).call(ServerValue, delta, MODULAR_DEPRECATION_ARG); +} + +export { ServerValue }; + +/** + * Logs debugging information to the console. Not implemented on native. + * @param _enabled - Whether logging is enabled. + * @param _persistent - Whether logging should persist. Optional. + */ +export function enableLogging(_enabled: boolean, _persistent?: boolean): void { + throw new Error('enableLogging() is not implemented'); +} + +export * from './modular/query'; +export * from './modular/transaction'; diff --git a/packages/database/lib/modular/index.d.ts b/packages/database/lib/modular/index.d.ts deleted file mode 100644 index be790654cd..0000000000 --- a/packages/database/lib/modular/index.d.ts +++ /dev/null @@ -1,246 +0,0 @@ -import { ReactNativeFirebase } from '@react-native-firebase/app'; -import { FirebaseDatabaseTypes } from '..'; - -import FirebaseApp = ReactNativeFirebase.FirebaseApp; -import Database = FirebaseDatabaseTypes.Module; -import DatabaseReference = FirebaseDatabaseTypes.Reference; - -/** - * Returns the instance of the Realtime Database SDK that is associated with - * the provided FirebaseApp. Initializes a new instance with - * default settings if no instance exists or if the existing - * instance uses a custom database URL. - * - * @param {FirebaseApp?} app - The FirebaseApp instance that the returned Realtime Database instance is associated with. - * @param {string?} url - * @returns {*} - */ -export declare function getDatabase(app?: FirebaseApp, url?: string): Database; - -/** - * Modify this Database instance to communicate with the Firebase Database emulator. - * This must be called synchronously immediately following the first call to firebase.database(). - * Do not use with production credentials as emulator traffic is not encrypted. - * - * Note: on android, hosts 'localhost' and '127.0.0.1' are automatically remapped to '10.0.2.2' (the - * "host" computer IP address for android emulators) to make the standard development experience easy. - * If you want to use the emulator on a real android device, you will need to specify the actual host - * computer IP address. - * - * @param db: The Database instance - * @param host: emulator host (eg, 'localhost') - * @param port: emulator port (eg, 9000) - */ -export declare function connectDatabaseEmulator( - db: Database, - host: string, - port: number, - // TODO: this exists in both the JS namespaced and modular versions of the SDK. - // But the RNFB namespaced version doesn't have it. - // options?: { - // mockUserToken?: EmulatorMockTokenOptions | string; - // }, -): void; - -/** - * Disconnects from the server (all Database operations will be completed offline). - * - * The client automatically maintains a persistent connection to the Database server, which - * will remain active indefinitely and reconnect when disconnected. However, the `goOffline()` and - * `goOnline()` methods may be used to control the client connection in cases where a persistent - * connection is undesirable. - * - * While offline, the client will no longer receive data updates from the Database. However, - * all Database operations performed locally will continue to immediately fire events, allowing - * your application to continue behaving normally. Additionally, each operation performed locally - * will automatically be queued and retried upon reconnection to the Database server. - * - * To reconnect to the Database and begin receiving remote events, see `goOnline()`. - * - * #### Example - * - * ```js - * const db = getDatabase();; - * await goOnline(db); - * ``` - */ -export declare function goOffline(db: Database): Promise; - -/** - * Reconnects to the server and synchronizes the offline Database state with the server state. - * - * This method should be used after disabling the active connection with `goOffline()`. Once - * reconnected, the client will transmit the proper data and fire the appropriate events so that - * your client "catches up" automatically. - * - * #### Example - * - * ```js - * const db = getDatabase(); - * await goOnline(db); - * ``` - */ -export declare function goOnline(db: Database): Promise; - -/** - * Returns a `Reference` representing the location in the Database corresponding to the provided path. - * If no path is provided, the Reference will point to the root of the Database. - * - * #### Example - * - * ```js - * const db = getDatabase(); - * - * // Get a reference to the root of the Database - * const rootRef = ref(db); - * - * // Get a reference to the /users/ada node - * const adaRef = ref(db, "users/ada"); - * ``` - * - * @param db The Database instance. - * @param path Optional path representing the location the returned `Reference` will point. If not provided, the returned `Reference` will point to the root of the Database. - */ -export declare function ref(db: Database, path?: string): DatabaseReference; - -/** - * Generates a Reference from a database URL. - * Note domain must be the same. - * Any query parameters are stripped as per the web SDK. - * - * @param db The Database instance. - * @param url The Firebase URL at which the returned Reference will point. - * @returns {DatabaseReference} - */ -export declare function refFromURL(db: Database, url: string): DatabaseReference; - -/** - * Sets whether persistence is enabled for all database calls for the current app - * instance. - * - * > Ensure this is called before any database calls are performed, otherwise - * persistence will only come into effect when the app is next started. - * - * #### Example - * - * ```js - * const db = getDatabase(); - * setPersistenceEnabled(db, true); - * - * async function bootstrap() { - * // Bootstrapping application - * const snapshot = await ref(db, "settings").once("value"); - * } - * ``` - * - * @param db The Database instance. - * @param enabled Whether persistence is enabled for the Database service. - */ -export declare function setPersistenceEnabled(db: Database, enabled: boolean): void; - -/** - * Sets the native logging level for the database module. By default, - * only warnings and errors are logged natively. Setting this to true will log all - * database events. - * - * > Ensure logging is disabled for production apps, as excessive logging can cause performance issues. - * - * #### Example - * - * ```js - * const db = getDatabase(); - * - * // Set debug logging if developing - * if (__DEV__) { - * setLoggingEnabled(db, true); - * } - * ``` - * - * @param db The Database instance. - * @param enabled Whether debug logging is enabled. - * @returns {void} - */ -export declare function setLoggingEnabled(db: Database, enabled: boolean): void; - -/** - * By default, Firebase Database will use up to 10MB of disk space to cache data. If the cache grows beyond this size, - * Firebase Database will start removing data that hasn't been recently used. If you find that your application - * caches too little or too much data, call this method to change the cache size. This method must be called before - * creating your first Database reference and only needs to be called once per application. - * - * Note that the specified cache size is only an approximation and the size on disk may temporarily exceed it at times. - * Cache sizes smaller than 1 MB or greater than 100 MB are not supported. - * - * #### Example - * - * ```js - * const db = getDatabase(); - * - * setPersistenceEnabled(db, true); - * setPersistenceCacheSizeBytes(db, 2000000); // 2MB - * - * async function bootstrap() { - * // Bootstrapping application - * const snapshot = await ref(db, 'settings').once("value"); - * } - * ``` - * - * @param db The Database instance. - * @param bytes The new size of the cache in bytes. - */ -export declare function setPersistenceCacheSizeBytes(db: Database, bytes: number): void; - -/** - * Force the use of longPolling instead of websockets. This will be ignored if websocket protocol is used in databaseURL. - */ -export declare function forceLongPolling(): void; - -/** - * Force the use of websockets instead of longPolling. - */ -export declare function forceWebSockets(): void; - -/** - * Returns a placeholder value for auto-populating the current timestamp (time - * since the Unix epoch, in milliseconds) as determined by the Firebase - * servers. - */ -export function serverTimestamp(): object; - -/** - * Returns the current Firebase Database server time as a JavaScript Date object. - */ -export function getServerTime(db: Database): Promise; -/** - * Returns a placeholder value that can be used to atomically increment the - * current database value by the provided delta. - * - * @param delta - the amount to modify the current value atomically. - * @returns A placeholder value for modifying data atomically server-side. - */ -export function increment(delta: number): object; - -/** - * Server specific values. - */ -export const ServerValue: { - /** - * A placeholder value for auto-populating the current timestamp. - */ - TIMESTAMP: object; - /** - * Returns a placeholder value that can be used to atomically increment the current database value. - */ - increment(delta: number): object; -}; - -/** - * Logs debugging information to the console. Not implemented on native. - * - * @param enabled - * @param persistent - */ -export declare function enableLogging(enabled: boolean, persistent?: boolean): any; - -export * from './query'; -export * from './transaction'; diff --git a/packages/database/lib/modular/index.js b/packages/database/lib/modular/index.js deleted file mode 100644 index deb9f173c9..0000000000 --- a/packages/database/lib/modular/index.js +++ /dev/null @@ -1,134 +0,0 @@ -import { getApp } from '@react-native-firebase/app'; -import DatabaseStatics from '../DatabaseStatics'; - -const { ServerValue } = DatabaseStatics; -import { MODULAR_DEPRECATION_ARG } from '@react-native-firebase/app/dist/module/common'; - -/** - * @typedef {import("..").FirebaseApp} FirebaseApp - * @typedef {import("..").FirebaseDatabaseTypes.Module} Database - */ - -/** - * @param {FirebaseApp?} app - The FirebaseApp instance that the returned Realtime Database instance is associated with. - * @param {string?} url - * @returns {Database} - */ -export function getDatabase(app, url) { - if (app) { - return getApp(app.name).database(url); - } - - return getApp().database(url); -} - -/** - * @param {Database} db - * @param {string} host - * @param {number} port - * @returns {void} - */ -export function connectDatabaseEmulator(db, host, port) { - db.useEmulator.call(db, host, port, MODULAR_DEPRECATION_ARG); -} - -/** - * @param {Database} db - * @returns {Promise} - */ -export function goOffline(db) { - return db.goOffline.call(db, MODULAR_DEPRECATION_ARG); -} - -/** - * @param {Database} db - * @returns {Promise} - */ -export function goOnline(db) { - return db.goOnline.call(db, MODULAR_DEPRECATION_ARG); -} - -/** - * @param {Database} db - * @param {string?} path - * @returns {DatabaseReference} - */ -export function ref(db, path) { - return db.ref.call(db, path, MODULAR_DEPRECATION_ARG); -} - -/** - * @param {Database} db - * @param {string} url - * @returns {DatabaseReference} - */ -export function refFromURL(db, url) { - return db.refFromURL.call(db, url, MODULAR_DEPRECATION_ARG); -} - -/** - * @param {Database} db - * @param {boolean} enabled - * @returns {void} - */ -export function setPersistenceEnabled(db, enabled) { - return db.setPersistenceEnabled.call(db, enabled, MODULAR_DEPRECATION_ARG); -} - -/** - * @param {Database} db - * @param {boolean} enabled - * @returns {void} - */ -export function setLoggingEnabled(db, enabled) { - return db.setLoggingEnabled.call(db, enabled, MODULAR_DEPRECATION_ARG); -} - -/** - * @param {Database} db - * @param {number} bytes - * @returns {void} - */ -export function setPersistenceCacheSizeBytes(db, bytes) { - return db.setPersistenceCacheSizeBytes.call(db, bytes, MODULAR_DEPRECATION_ARG); -} - -export function forceLongPolling() { - throw new Error('forceLongPolling() is not implemented'); -} - -export function forceWebSockets() { - throw new Error('forceWebSockets() is not implemented'); -} - -/** - * @param {Database} db - * @returns {Date} - */ -export function getServerTime(db) { - return db.getServerTime.call(db, MODULAR_DEPRECATION_ARG); -} - -/** - * @returns {object} - */ -export function serverTimestamp() { - return ServerValue.TIMESTAMP; -} - -/** - * @param {number} delta - * @returns {object} - */ -export function increment(delta) { - return ServerValue.increment.call(ServerValue, delta, MODULAR_DEPRECATION_ARG); -} - -export { ServerValue }; - -export function enableLogging(_enabled, _persistent) { - throw new Error('enableLogging() is not implemented'); -} - -export * from './query'; -export * from './transaction'; diff --git a/packages/database/lib/modular/query.d.ts b/packages/database/lib/modular/query.d.ts deleted file mode 100644 index 00d44e3508..0000000000 --- a/packages/database/lib/modular/query.d.ts +++ /dev/null @@ -1,972 +0,0 @@ -import { FirebaseDatabaseTypes } from '../..'; - -export type Query = FirebaseDatabaseTypes.Query; -export type DataSnapshot = FirebaseDatabaseTypes.DataSnapshot; -export type DatabaseReference = FirebaseDatabaseTypes.Reference; -export type OnDisconnect = FirebaseDatabaseTypes.OnDisconnect; - -/** - * A `Promise` that can also act as a `DatabaseReference` when returned by - * {@link push}. The reference is available immediately and the `Promise` resolves - * as the write to the backend completes. - */ -export interface ThenableReference - extends DatabaseReference, Pick, 'then' | 'catch'> {} - -export type Unsubscribe = () => void; - -export interface ListenOptions { - readonly onlyOnce?: boolean; -} - -/** Describes the different query constraints available in this SDK. */ -export type QueryConstraintType = - | 'endAt' - | 'endBefore' - | 'startAt' - | 'startAfter' - | 'limitToFirst' - | 'limitToLast' - | 'orderByChild' - | 'orderByKey' - | 'orderByPriority' - | 'orderByValue' - | 'equalTo'; - -/** - * A `QueryConstraint` is used to narrow the set of documents returned by a - * Database query. `QueryConstraint`s are created by invoking {@link endAt}, - * {@link endBefore}, {@link startAt}, {@link startAfter}, {@link - * limitToFirst}, {@link limitToLast}, {@link orderByChild}, - * {@link orderByChild}, {@link orderByKey} , {@link orderByPriority} , - * {@link orderByValue} or {@link equalTo} and - * can then be passed to {@link query} to create a new query instance that - * also contains this `QueryConstraint`. - */ -export interface QueryConstraint { - /** The type of this query constraints */ - readonly _type: QueryConstraintType; - - _apply(query: Query): Query; -} - -/** - * Creates a `QueryConstraint` with the specified ending point. - * - * Using `startAt()`, `startAfter()`, `endBefore()`, `endAt()` and `equalTo()` - * allows you to choose arbitrary starting and ending points for your queries. - * - * The ending point is inclusive, so children with exactly the specified value - * will be included in the query. The optional key argument can be used to - * further limit the range of the query. If it is specified, then children that - * have exactly the specified value must also have a key name less than or equal - * to the specified key. - * - * @param value - The value to end at. The argument type depends on which - * `orderBy*()` function was used in this query. Specify a value that matches - * the `orderBy*()` type. When used in combination with `orderByKey()`, the - * value must be a string. - * @param key - The child key to end at, among the children with the previously - * specified priority. This argument is only allowed if ordering by child, - * value, or priority. - */ -export declare function endAt( - value: number | string | boolean | null, - key?: string, -): QueryConstraint; - -/** - * Creates a QueryConstraint with the specified ending point (exclusive). - * - * Using `startAt()`, `startAfter()`, `endBefore()`, `endAt()` and `equalTo()` allows you to - * choose arbitrary starting and ending points for your queries. - * - * The ending point is exclusive. If only a value is provided, children with a - * value less than the specified value will be included in the query. If a key - * is specified, then children must have a value less than or equal to the - * specified value and a key name less than the specified key. - * - * @param value - The value to end before. The argument type depends on which - * `orderBy*()` function was used in this query. Specify a value that matches - * the `orderBy*()` type. When used in combination with `orderByKey()`, the - * value must be a string. - * @param key - The child key to end before, among the children with the - * previously specified priority. This argument is only allowed if ordering by - * child, value, or priority. - */ -export declare function endBefore( - value: number | string | boolean | null, - key?: string, -): QueryConstraint; - -/** - * Creates a QueryConstraint that includes children that match the specified value. - * - * Using `startAt()`, `startAfter()`, `endBefore()`, `endAt()` and `equalTo()` allows - * you to choose arbitrary starting and ending points for your queries. - * - * The optional key argument can be used to further limit the range of the - * query. If it is specified, then children that have exactly the specified - * value must also have exactly the specified key as their key name. This - * can be used to filter result sets with many matches for the same value. - * - * @param value - The value to match for. The argument type depends on which - * `orderBy*()` function was used in this query. Specify a value that matches - * the `orderBy*()` type. When used in combination with `orderByKey()`, the - * value must be a string. - * @param key - The child key to start at, among the children with the - * previously specified priority. This argument is only allowed if ordering by - * child, value, or priority. - */ -export declare function equalTo( - value: number | string | boolean | null, - key?: string, -): QueryConstraint; - -/** - * Creates a QueryConstraint with the specified starting point. - * - * Using `startAt()`, `startAfter()`, `endBefore()`, `endAt()` and `equalTo()` - * allows you to choose arbitrary starting and ending points for your queries. - * - * The starting point is inclusive, so children with exactly the specified - * value will be included in the query. The optional key argument can be used - * to further limit the range of the query. If it is specified, then children - * that have exactly the specified value must also have a key name greater than - * or equal to the specified key. - * - * @param value - The value to start at. The argument type depends on which - * `orderBy*()` function was used in this query. Specify a value that matches - * the `orderBy*()` type. When used in combination with `orderByKey()`, the - * value must be a string. - * @param key - The child key to start at. This argument is only allowed if - * ordering by child, value, or priority. - */ -export declare function startAt( - value?: number | string | boolean | null, - key?: string, -): QueryConstraint; - -/** - * Creates a `QueryConstraint` with the specified starting point (exclusive). - * - * Using `startAt()`, `startAfter()`, `endBefore()`, `endAt()` and `equalTo()` - * allows you to choose arbitrary starting and ending points for your queries. - * - * The starting point is exclusive. If only a value is provided, children - * with a value greater than the specified value will be included in the query. - * If a key is specified, then children must have a value greater than or equal - * to the specified value and a a key name greater than the specified key. - * - * @param value - The value to start after. The argument type depends on which - * `orderBy*()` function was used in this query. Specify a value that matches - * the `orderBy*()` type. When used in combination with `orderByKey()`, the - * value must be a string. - * @param key - The child key to start after. This argument is only allowed if - * ordering by child, value, or priority. - */ -export function startAfter(value: number | string | boolean | null, key?: string): QueryConstraint; - -/** - * Creates a new `QueryConstraint` that if limited to the first specific number - * of children. - * - * The `limitToFirst()` method is used to set a maximum number of children to be - * synced for a given callback. If we set a limit of 100, we will initially only - * receive up to 100 `child_added` events. If we have fewer than 100 messages - * stored in our Database, a `child_added` event will fire for each message. - * However, if we have over 100 messages, we will only receive a `child_added` - * event for the first 100 ordered messages. As items change, we will receive - * `child_removed` events for each item that drops out of the active list so - * that the total number stays at 100. - * - * @param limit - The maximum number of nodes to include in this query. - */ -export function limitToFirst(limit: number): QueryConstraint; - -/** - * Creates a new `QueryConstraint` that is limited to return only the last - * specified number of children. - * - * The `limitToLast()` method is used to set a maximum number of children to be - * synced for a given callback. If we set a limit of 100, we will initially only - * receive up to 100 `child_added` events. If we have fewer than 100 messages - * stored in our Database, a `child_added` event will fire for each message. - * However, if we have over 100 messages, we will only receive a `child_added` - * event for the last 100 ordered messages. As items change, we will receive - * `child_removed` events for each item that drops out of the active list so - * that the total number stays at 100. - * - * @param limit - The maximum number of nodes to include in this query. - */ -export function limitToLast(limit: number): QueryConstraint; - -/** - * Creates a new `QueryConstraint` that orders 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. - * - * Firebase queries allow you to order your data by any child key on the fly. - * However, if you know in advance what your indexes will be, you can define - * them via the .indexOn rule in your Security Rules for better performance. - * - * @param path - The path to order by. - */ -export function orderByChild(path: string): QueryConstraint; - -/** - * Creates a new `QueryConstraint` that orders by the key. - * - * Sorts the results of a query by their (ascending) key values. - */ -export function orderByKey(): QueryConstraint; - -/** - * Creates a new `QueryConstraint` that orders by priority. - * - * Applications need not use priority but can order collections by - * ordinary properties - */ -export function orderByPriority(): QueryConstraint; - -/** - * Creates a new `QueryConstraint` that orders 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. - */ -export function orderByValue(): QueryConstraint; - -/** - * Creates a new immutable instance of `Query` that is extended to also include - * additional query constraints. - * - * @param query - The Query instance to use as a base for the new constraints. - * @param queryConstraints - The list of `QueryConstraint`s to apply. - * @throws if any of the provided query constraints cannot be combined with the - * existing or new constraints. - */ -export function query(query: Query, ...queryConstraints: QueryConstraint[]): Query; - -/** - * Listens for data changes at a particular location. - * - * This is the primary way to read data from a Database. Your callback - * will be triggered for the initial data and again whenever the data changes. - * Invoke the returned unsubscribe callback to stop receiving updates. - * - * An `onValue` event will trigger once with the initial data stored at this - * location, and then trigger again each time the data changes. The - * `DataSnapshot` passed to the callback will be for the location at which - * `on()` was called. It won't trigger until the entire contents has been - * synchronized. If the location has no data, it will be triggered with an empty - * `DataSnapshot` (`val()` will return `null`). - * - * @param query - The query to run. - * @param callback - A callback that fires when the specified event occurs. The - * callback will be passed a DataSnapshot. - * @param cancelCallback - An optional callback that will be notified if your - * event subscription is ever canceled because your client does not have - * permission to read this data (or it had permission but has now lost it). - * This callback will be passed an `Error` object indicating why the failure - * occurred. - * @returns A function that can be invoked to remove the listener. - */ -export function onValue( - query: Query, - callback: (snapshot: DataSnapshot) => unknown, - cancelCallback?: (error: Error) => unknown, -): Unsubscribe; - -/** - * Listens for data changes at a particular location. - * - * This is the primary way to read data from a Database. Your callback - * will be triggered for the initial data and again whenever the data changes. - * Invoke the returned unsubscribe callback to stop receiving updates. - * - * An `onValue` event will trigger once with the initial data stored at this - * location, and then trigger again each time the data changes. The - * `DataSnapshot` passed to the callback will be for the location at which - * `on()` was called. It won't trigger until the entire contents has been - * synchronized. If the location has no data, it will be triggered with an empty - * `DataSnapshot` (`val()` will return `null`). - * - * @param query - The query to run. - * @param callback - A callback that fires when the specified event occurs. The - * callback will be passed a DataSnapshot. - * @param options - An object that can be used to configure `onlyOnce`, which - * then removes the listener after its first invocation. - * @returns A function that can be invoked to remove the listener. - */ -export function onValue( - query: Query, - callback: (snapshot: DataSnapshot) => unknown, - options: ListenOptions, -): Unsubscribe; - -/** - * Listens for data changes at a particular location. - * - * This is the primary way to read data from a Database. Your callback - * will be triggered for the initial data and again whenever the data changes. - * Invoke the returned unsubscribe callback to stop receiving updates. - * - * An `onValue` event will trigger once with the initial data stored at this - * location, and then trigger again each time the data changes. The - * `DataSnapshot` passed to the callback will be for the location at which - * `on()` was called. It won't trigger until the entire contents has been - * synchronized. If the location has no data, it will be triggered with an empty - * `DataSnapshot` (`val()` will return `null`). - * - * @param query - The query to run. - * @param callback - A callback that fires when the specified event occurs. The - * callback will be passed a DataSnapshot. - * @param cancelCallback - An optional callback that will be notified if your - * event subscription is ever canceled because your client does not have - * permission to read this data (or it had permission but has now lost it). - * This callback will be passed an `Error` object indicating why the failure - * occurred. - * @param options - An object that can be used to configure `onlyOnce`, which - * then removes the listener after its first invocation. - * @returns A function that can be invoked to remove the listener. - */ -export function onValue( - query: Query, - callback: (snapshot: DataSnapshot) => unknown, - cancelCallback: (error: Error) => unknown, - options: ListenOptions, -): Unsubscribe; - -export function onValue( - query: Query, - callback: (snapshot: DataSnapshot) => unknown, - cancelCallbackOrListenOptions?: ((error: Error) => unknown) | ListenOptions, - options?: ListenOptions, -): Unsubscribe; - -/** - * Listens for data changes at a particular location. - * - * This is the primary way to read data from a Database. Your callback - * will be triggered for the initial data and again whenever the data changes. - * Invoke the returned unsubscribe callback to stop receiving updates. - * - * An `onChildAdded` event will be triggered once for each initial child at this - * location, and it will be triggered again every time a new child is added. The - * `DataSnapshot` passed into the callback will reflect the data for the - * relevant child. For ordering purposes, it is passed a second argument which - * is a string containing the key of the previous sibling child by sort order, - * or `null` if it is the first child. - * - * @param query - The query to run. - * @param callback - A callback that fires when the specified event occurs. - * The callback will be passed a DataSnapshot and a string containing the key of - * the previous child, by sort order, or `null` if it is the first child. - * @param cancelCallback - An optional callback that will be notified if your - * event subscription is ever canceled because your client does not have - * permission to read this data (or it had permission but has now lost it). - * This callback will be passed an `Error` object indicating why the failure - * occurred. - * @returns A function that can be invoked to remove the listener. - */ -export function onChildAdded( - query: Query, - callback: (snapshot: DataSnapshot, previousChildName?: string | null) => unknown, - cancelCallback?: (error: Error) => unknown, -): Unsubscribe; - -/** - * Listens for data changes at a particular location. - * - * This is the primary way to read data from a Database. Your callback - * will be triggered for the initial data and again whenever the data changes. - * Invoke the returned unsubscribe callback to stop receiving updates. - * - * An `onChildAdded` event will be triggered once for each initial child at this - * location, and it will be triggered again every time a new child is added. The - * `DataSnapshot` passed into the callback will reflect the data for the - * relevant child. For ordering purposes, it is passed a second argument which - * is a string containing the key of the previous sibling child by sort order, - * or `null` if it is the first child. - * - * @param query - The query to run. - * @param callback - A callback that fires when the specified event occurs. - * The callback will be passed a DataSnapshot and a string containing the key of - * the previous child, by sort order, or `null` if it is the first child. - * @param options - An object that can be used to configure `onlyOnce`, which - * then removes the listener after its first invocation. - * @returns A function that can be invoked to remove the listener. - */ -export function onChildAdded( - query: Query, - callback: (snapshot: DataSnapshot, previousChildName: string | null) => unknown, - options: ListenOptions, -): Unsubscribe; - -/** - * Listens for data changes at a particular location. - * - * This is the primary way to read data from a Database. Your callback - * will be triggered for the initial data and again whenever the data changes. - * Invoke the returned unsubscribe callback to stop receiving updates. - * - * An `onChildAdded` event will be triggered once for each initial child at this - * location, and it will be triggered again every time a new child is added. The - * `DataSnapshot` passed into the callback will reflect the data for the - * relevant child. For ordering purposes, it is passed a second argument which - * is a string containing the key of the previous sibling child by sort order, - * or `null` if it is the first child. - * - * @param query - The query to run. - * @param callback - A callback that fires when the specified event occurs. - * The callback will be passed a DataSnapshot and a string containing the key of - * the previous child, by sort order, or `null` if it is the first child. - * @param cancelCallback - An optional callback that will be notified if your - * event subscription is ever canceled because your client does not have - * permission to read this data (or it had permission but has now lost it). - * This callback will be passed an `Error` object indicating why the failure - * occurred. - * @param options - An object that can be used to configure `onlyOnce`, which - * then removes the listener after its first invocation. - * @returns A function that can be invoked to remove the listener. - */ -export function onChildAdded( - query: Query, - callback: (snapshot: DataSnapshot, previousChildName: string | null) => unknown, - cancelCallback: (error: Error) => unknown, - options: ListenOptions, -): Unsubscribe; - -export function onChildAdded( - query: Query, - callback: (snapshot: DataSnapshot, previousChildName: string | null) => unknown, - cancelCallbackOrListenOptions?: ((error: Error) => unknown) | ListenOptions, - options?: ListenOptions, -): Unsubscribe; - -/** - * Listens for data changes at a particular location. - * - * This is the primary way to read data from a Database. Your callback - * will be triggered for the initial data and again whenever the data changes. - * Invoke the returned unsubscribe callback to stop receiving updates. - * - * An `onChildChanged` event will be triggered when the data stored in a child - * (or any of its descendants) changes. Note that a single `child_changed` event - * may represent multiple changes to the child. The `DataSnapshot` passed to the - * callback will contain the new child contents. For ordering purposes, the - * callback is also passed a second argument which is a string containing the - * key of the previous sibling child by sort order, or `null` if it is the first - * child. - * - * @param query - The query to run. - * @param callback - A callback that fires when the specified event occurs. - * The callback will be passed a DataSnapshot and a string containing the key of - * the previous child, by sort order, or `null` if it is the first child. - * @param cancelCallback - An optional callback that will be notified if your - * event subscription is ever canceled because your client does not have - * permission to read this data (or it had permission but has now lost it). - * This callback will be passed an `Error` object indicating why the failure - * occurred. - * @returns A function that can be invoked to remove the listener. - */ -export function onChildChanged( - query: Query, - callback: (snapshot: DataSnapshot, previousChildName: string | null) => unknown, - cancelCallback?: (error: Error) => unknown, -): Unsubscribe; - -/** - * Listens for data changes at a particular location. - * - * This is the primary way to read data from a Database. Your callback - * will be triggered for the initial data and again whenever the data changes. - * Invoke the returned unsubscribe callback to stop receiving updates. - * - * An `onChildChanged` event will be triggered when the data stored in a child - * (or any of its descendants) changes. Note that a single `child_changed` event - * may represent multiple changes to the child. The `DataSnapshot` passed to the - * callback will contain the new child contents. For ordering purposes, the - * callback is also passed a second argument which is a string containing the - * key of the previous sibling child by sort order, or `null` if it is the first - * child. - * - * @param query - The query to run. - * @param callback - A callback that fires when the specified event occurs. - * The callback will be passed a DataSnapshot and a string containing the key of - * the previous child, by sort order, or `null` if it is the first child. - * @param options - An object that can be used to configure `onlyOnce`, which - * then removes the listener after its first invocation. - * @returns A function that can be invoked to remove the listener. - */ -export function onChildChanged( - query: Query, - callback: (snapshot: DataSnapshot, previousChildName: string | null) => unknown, - options: ListenOptions, -): Unsubscribe; - -/** - * Listens for data changes at a particular location. - * - * This is the primary way to read data from a Database. Your callback - * will be triggered for the initial data and again whenever the data changes. - * Invoke the returned unsubscribe callback to stop receiving updates. - * - * An `onChildChanged` event will be triggered when the data stored in a child - * (or any of its descendants) changes. Note that a single `child_changed` event - * may represent multiple changes to the child. The `DataSnapshot` passed to the - * callback will contain the new child contents. For ordering purposes, the - * callback is also passed a second argument which is a string containing the - * key of the previous sibling child by sort order, or `null` if it is the first - * child. - * - * @param query - The query to run. - * @param callback - A callback that fires when the specified event occurs. - * The callback will be passed a DataSnapshot and a string containing the key of - * the previous child, by sort order, or `null` if it is the first child. - * @param cancelCallback - An optional callback that will be notified if your - * event subscription is ever canceled because your client does not have - * permission to read this data (or it had permission but has now lost it). - * This callback will be passed an `Error` object indicating why the failure - * occurred. - * @param options - An object that can be used to configure `onlyOnce`, which - * then removes the listener after its first invocation. - * @returns A function that can be invoked to remove the listener. - */ -export function onChildChanged( - query: Query, - callback: (snapshot: DataSnapshot, previousChildName: string | null) => unknown, - cancelCallback: (error: Error) => unknown, - options: ListenOptions, -): Unsubscribe; - -export function onChildChanged( - query: Query, - callback: (snapshot: DataSnapshot, previousChildName: string | null) => unknown, - cancelCallbackOrListenOptions?: ((error: Error) => unknown) | ListenOptions, - options?: ListenOptions, -): Unsubscribe; - -/** - * Listens for data changes at a particular location. - * - * This is the primary way to read data from a Database. Your callback - * will be triggered for the initial data and again whenever the data changes. - * Invoke the returned unsubscribe callback to stop receiving updates. - * - * An `onChildMoved` event will be triggered when a child's sort order changes - * such that its position relative to its siblings changes. The `DataSnapshot` - * passed to the callback will be for the data of the child that has moved. It - * is also passed a second argument which is a string containing the key of the - * previous sibling child by sort order, or `null` if it is the first child. - * - * @param query - The query to run. - * @param callback - A callback that fires when the specified event occurs. - * The callback will be passed a DataSnapshot and a string containing the key of - * the previous child, by sort order, or `null` if it is the first child. - * @param cancelCallback - An optional callback that will be notified if your - * event subscription is ever canceled because your client does not have - * permission to read this data (or it had permission but has now lost it). - * This callback will be passed an `Error` object indicating why the failure - * occurred. - * @returns A function that can be invoked to remove the listener. - */ -export function onChildMoved( - query: Query, - callback: (snapshot: DataSnapshot, previousChildName: string | null) => unknown, - cancelCallback?: (error: Error) => unknown, -): Unsubscribe; - -/** - * Listens for data changes at a particular location. - * - * This is the primary way to read data from a Database. Your callback - * will be triggered for the initial data and again whenever the data changes. - * Invoke the returned unsubscribe callback to stop receiving updates. - * - * An `onChildMoved` event will be triggered when a child's sort order changes - * such that its position relative to its siblings changes. The `DataSnapshot` - * passed to the callback will be for the data of the child that has moved. It - * is also passed a second argument which is a string containing the key of the - * previous sibling child by sort order, or `null` if it is the first child. - * - * @param query - The query to run. - * @param callback - A callback that fires when the specified event occurs. - * The callback will be passed a DataSnapshot and a string containing the key of - * the previous child, by sort order, or `null` if it is the first child. - * @param options - An object that can be used to configure `onlyOnce`, which - * then removes the listener after its first invocation. - * @returns A function that can be invoked to remove the listener. - */ -export function onChildMoved( - query: Query, - callback: (snapshot: DataSnapshot, previousChildName: string | null) => unknown, - options: ListenOptions, -): Unsubscribe; - -/** - * Listens for data changes at a particular location. - * - * This is the primary way to read data from a Database. Your callback - * will be triggered for the initial data and again whenever the data changes. - * Invoke the returned unsubscribe callback to stop receiving updates. - * - * An `onChildMoved` event will be triggered when a child's sort order changes - * such that its position relative to its siblings changes. The `DataSnapshot` - * passed to the callback will be for the data of the child that has moved. It - * is also passed a second argument which is a string containing the key of the - * previous sibling child by sort order, or `null` if it is the first child. - * - * @param query - The query to run. - * @param callback - A callback that fires when the specified event occurs. - * The callback will be passed a DataSnapshot and a string containing the key of - * the previous child, by sort order, or `null` if it is the first child. - * @param cancelCallback - An optional callback that will be notified if your - * event subscription is ever canceled because your client does not have - * permission to read this data (or it had permission but has now lost it). - * This callback will be passed an `Error` object indicating why the failure - * occurred. - * @param options - An object that can be used to configure `onlyOnce`, which - * then removes the listener after its first invocation. - * @returns A function that can be invoked to remove the listener. - */ -export function onChildMoved( - query: Query, - callback: (snapshot: DataSnapshot, previousChildName: string | null) => unknown, - cancelCallback: (error: Error) => unknown, - options: ListenOptions, -): Unsubscribe; - -export function onChildMoved( - query: Query, - callback: (snapshot: DataSnapshot, previousChildName: string | null) => unknown, - cancelCallbackOrListenOptions?: ((error: Error) => unknown) | ListenOptions, - options?: ListenOptions, -): Unsubscribe; - -/** - * Listens for data changes at a particular location. - * - * This is the primary way to read data from a Database. Your callback - * will be triggered for the initial data and again whenever the data changes. - * Invoke the returned unsubscribe callback to stop receiving updates. See - * {@link https://firebase.google.com/docs/database/web/retrieve-data | Retrieve Data on the Web} - * for more details. - * - * An `onChildRemoved` event will be triggered once every time a child is - * removed. The `DataSnapshot` passed into the callback will be the old data for - * the child that was removed. A child will get removed when either: - * - * - a client explicitly calls `remove()` on that child or one of its ancestors - * - a client calls `set(null)` on that child or one of its ancestors - * - that child has all of its children removed - * - there is a query in effect which now filters out the child (because it's - * sort order changed or the max limit was hit) - * - * @param query - The query to run. - * @param callback - A callback that fires when the specified event occurs. - * The callback will be passed a DataSnapshot and a string containing the key of - * the previous child, by sort order, or `null` if it is the first child. - * @param cancelCallback - An optional callback that will be notified if your - * event subscription is ever canceled because your client does not have - * permission to read this data (or it had permission but has now lost it). - * This callback will be passed an `Error` object indicating why the failure - * occurred. - * @returns A function that can be invoked to remove the listener. - */ -export function onChildRemoved( - query: Query, - callback: (snapshot: DataSnapshot) => unknown, - cancelCallback?: (error: Error) => unknown, -): Unsubscribe; - -/** - * Listens for data changes at a particular location. - * - * This is the primary way to read data from a Database. Your callback - * will be triggered for the initial data and again whenever the data changes. - * Invoke the returned unsubscribe callback to stop receiving updates. See - * {@link https://firebase.google.com/docs/database/web/retrieve-data | Retrieve Data on the Web} - * for more details. - * - * An `onChildRemoved` event will be triggered once every time a child is - * removed. The `DataSnapshot` passed into the callback will be the old data for - * the child that was removed. A child will get removed when either: - * - * - a client explicitly calls `remove()` on that child or one of its ancestors - * - a client calls `set(null)` on that child or one of its ancestors - * - that child has all of its children removed - * - there is a query in effect which now filters out the child (because it's - * sort order changed or the max limit was hit) - * - * @param query - The query to run. - * @param callback - A callback that fires when the specified event occurs. - * The callback will be passed a DataSnapshot and a string containing the key of - * the previous child, by sort order, or `null` if it is the first child. - * @param options - An object that can be used to configure `onlyOnce`, which - * then removes the listener after its first invocation. - * @returns A function that can be invoked to remove the listener. - */ -export function onChildRemoved( - query: Query, - callback: (snapshot: DataSnapshot) => unknown, - options: ListenOptions, -): Unsubscribe; - -/** - * Listens for data changes at a particular location. - * - * This is the primary way to read data from a Database. Your callback - * will be triggered for the initial data and again whenever the data changes. - * Invoke the returned unsubscribe callback to stop receiving updates. See - * {@link https://firebase.google.com/docs/database/web/retrieve-data | Retrieve Data on the Web} - * for more details. - * - * An `onChildRemoved` event will be triggered once every time a child is - * removed. The `DataSnapshot` passed into the callback will be the old data for - * the child that was removed. A child will get removed when either: - * - * - a client explicitly calls `remove()` on that child or one of its ancestors - * - a client calls `set(null)` on that child or one of its ancestors - * - that child has all of its children removed - * - there is a query in effect which now filters out the child (because it's - * sort order changed or the max limit was hit) - * - * @param query - The query to run. - * @param callback - A callback that fires when the specified event occurs. - * The callback will be passed a DataSnapshot and a string containing the key of - * the previous child, by sort order, or `null` if it is the first child. - * @param cancelCallback - An optional callback that will be notified if your - * event subscription is ever canceled because your client does not have - * permission to read this data (or it had permission but has now lost it). - * This callback will be passed an `Error` object indicating why the failure - * occurred. - * @param options - An object that can be used to configure `onlyOnce`, which - * then removes the listener after its first invocation. - * @returns A function that can be invoked to remove the listener. - */ -export function onChildRemoved( - query: Query, - callback: (snapshot: DataSnapshot) => unknown, - cancelCallback: (error: Error) => unknown, - options: ListenOptions, -): Unsubscribe; - -export function onChildRemoved( - query: Query, - callback: (snapshot: DataSnapshot) => unknown, - cancelCallbackOrListenOptions?: ((error: Error) => unknown) | ListenOptions, - options?: ListenOptions, -): Unsubscribe; - -/** - * Writes data to this Database location. - * - * This will overwrite any data at this location and all child locations. - * - * The effect of the write will be visible immediately, and the corresponding - * events ("value", "child_added", etc.) will be triggered. Synchronization of - * the data to the Firebase servers will also be started, and the returned - * Promise will resolve when complete. If provided, the `onComplete` callback - * will be called asynchronously after synchronization has finished. - * - * Passing `null` for the new value is equivalent to calling `remove()`; namely, - * all data at this location and all child locations will be deleted. - * - * `set()` will remove any priority stored at this location, so if priority is - * meant to be preserved, you need to use `setWithPriority()` instead. - * - * Note that modifying data with `set()` will cancel any pending transactions - * at that location, so extreme care should be taken if mixing `set()` and - * `transaction()` to modify the same data. - * - * A single `set()` will generate a single "value" event at the location where - * the `set()` was performed. - * - * @param ref - The location to write to. - * @param value - The value to be written (string, number, boolean, object, - * array, or null). - * @returns Resolves when write to server is complete. - */ -export function set(ref: DatabaseReference, value: unknown): Promise; - -/** - * Sets a priority for the data at this Database location. - * - * Applications need not use priority but can order collections by - * ordinary properties - * - * @param ref - The location to write to. - * @param priority - The priority to be written (string, number, or null). - * @returns Resolves when write to server is complete. - */ -export function setPriority( - ref: DatabaseReference, - priority: string | number | null, -): Promise; - -/** - * Writes data the Database location. Like `set()` but also specifies the - * priority for that data. - * - * Applications need not use priority but can order collections by - * ordinary properties - * - * @param ref - The location to write to. - * @param value - The value to be written (string, number, boolean, object, - * array, or null). - * @param priority - The priority to be written (string, number, or null). - * @returns Resolves when write to server is complete. - */ -export function setWithPriority( - ref: DatabaseReference, - value: unknown, - priority: string | number | null, -): Promise; - -/** - * Gets the most up-to-date result for this query. - * - * @param query - The query to run. - * @returns A `Promise` which resolves to the resulting DataSnapshot if a value is - * available, or rejects if the client is unable to return a value (e.g., if the - * server is unreachable and there is nothing cached). - */ -export function get(query: Query): Promise; - -/** - * Detaches a callback previously attached with the corresponding on*() (onValue, onChildAdded) listener. - * Note: This is not the recommended way to remove a listener. Instead, please use the returned callback function from the respective on* callbacks. - * Detach a callback previously attached with on*(). Calling off() on a parent listener will not automatically remove listeners registered on child nodes, off() must also be called on any child listeners to remove the callback. - * If a callback is not specified, all callbacks for the specified eventType will be removed. - * Similarly, if no eventType is specified, all callbacks for the Reference will be removed. - * Individual listeners can also be removed by invoking their unsubscribe callbacks. - * Note: Not implemented on native - * - * @param query - The query to run - * @param eventType One of the following strings: "value", "child_added", "child_changed", "child_removed", or "child_moved. - * @param callback - */ -export declare function off( - query: Query, - eventType?: EventType, - callback?: (snapshot: DataSnapshot, previousChildName?: string | null) => unknown, -): void; - -/** - * Gets a `Reference` for the location at the specified relative path. - * - * The relative path can either be a simple child name (for example, "ada") or - * a deeper slash-separated path (for example, "ada/name/first"). - * - * @param parent - The parent location. - * @param path - A relative path from this location to the desired child - * location. - * @returns The specified child location. - */ -export function child(parent: DatabaseReference, path: string): DatabaseReference; - -/** - * Returns an `OnDisconnect` object - see - * {@link https://firebase.google.com/docs/database/web/offline-capabilities | Enabling Offline Capabilities in JavaScript} - * for more information on how to use it. - * - * @param ref - The reference to add OnDisconnect triggers for. - */ -export function onDisconnect(ref: DatabaseReference): OnDisconnect; - -/** - * By calling `keepSynced(true)` on a location, the data for that location will automatically - * be downloaded and kept in sync, even when no listeners are attached for that location. - * - * #### Example - * - * ```js - * const dbRef = ref(getDatabase(), 'users'); - * await keepSynced(dbRef, true); - * ``` - * - * @param ref A location to keep synchronized. - * @param bool Pass `true` to keep this location synchronized, pass `false` to stop synchronization. - */ -export function keepSynced(ref: DatabaseReference, bool: boolean): Promise; - -/** - * Generates a new child location using a unique key and returns its - * `Reference`. - * - * This is the most common pattern for adding data to a collection of items. - * - * If you provide a value to `push()`, the value is written to the - * generated location. If you don't pass a value, nothing is written to the - * database and the child remains empty (but you can use the `Reference` - * elsewhere). - * - * The unique keys generated by `push()` are ordered by the current time, so the - * resulting list of items is chronologically sorted. The keys are also - * designed to be unguessable (they contain 72 random bits of entropy). - * - * See {@link https://firebase.google.com/docs/database/web/lists-of-data#append_to_a_list_of_data | Append to a list of data}. - * See {@link https://firebase.googleblog.com/2015/02/the-2120-ways-to-ensure-unique_68.html | The 2^120 Ways to Ensure Unique Identifiers}. - * - * @param parent - The parent location. - * @param value - Optional value to be written at the generated location. - * @returns Combined `Promise` and `Reference`; resolves when write is complete, - * but can be used immediately as the `Reference` to the child location. - */ -export function push(parent: DatabaseReference, value?: unknown): ThenableReference; - -/** - * Removes the data at this Database location. - * - * Any data at child locations will also be deleted. - * - * The effect of the remove will be visible immediately and the corresponding - * event 'value' will be triggered. Synchronization of the remove to the - * Firebase servers will also be started, and the returned Promise will resolve - * when complete. If provided, the onComplete callback will be called - * asynchronously after synchronization has finished. - * - * @param ref - The location to remove. - * @returns Resolves when remove on server is complete. - */ -export function remove(ref: DatabaseReference): Promise; - -/** - * Writes multiple values to the Database at once. - * - * The `values` argument contains multiple property-value pairs that will be - * written to the Database together. Each child property can either be a simple - * property (for example, "name") or a relative path (for example, - * "name/first") from the current location to the data to update. - * - * As opposed to the `set()` method, `update()` can be use to selectively update - * only the referenced properties at the current location (instead of replacing - * all the child properties at the current location). - * - * The effect of the write will be visible immediately, and the corresponding - * events ('value', 'child_added', etc.) will be triggered. Synchronization of - * the data to the Firebase servers will also be started, and the returned - * Promise will resolve when complete. If provided, the `onComplete` callback - * will be called asynchronously after synchronization has finished. - * - * A single `update()` will generate a single "value" event at the location - * where the `update()` was performed, regardless of how many children were - * modified. - * - * Note that modifying data with `update()` will cancel any pending - * transactions at that location, so extreme care should be taken if mixing - * `update()` and `transaction()` to modify the same data. - * - * Passing `null` to `update()` will remove the data at this location. - * - * See - * {@link https://firebase.googleblog.com/2015/09/introducing-multi-location-updates-and_86.html | Introducing multi-location updates and more}. - * - * @param ref - The location to write to. - * @param values - Object containing multiple values. - * @returns Resolves when update on server is complete. - */ -export function update(ref: DatabaseReference, values: object): Promise; diff --git a/packages/database/lib/modular/query.js b/packages/database/lib/modular/query.js deleted file mode 100644 index a3c8e16998..0000000000 --- a/packages/database/lib/modular/query.js +++ /dev/null @@ -1,305 +0,0 @@ -/** - * @typedef {import('../..').DatabaseReference} DatabaseReference - * @typedef {import('../..').DataSnapshot} DataSnapshot - * @typedef {import('./query').QueryConstraint} IQueryConstraint - * @typedef {import('./query').Query} Query - * @typedef {import('./query').OnDisconnect} OnDisconnect - * @typedef {import('./query').ListenOptions} ListenOptions - * @typedef {import('./query').Unsubscribe} Unsubscribe - * @typedef {import('./query').EventType} EventType - * @typedef {import('./query').ThenableReference} ThenableReference - */ - -/** - * @implements {IQueryConstraint} - */ - -import { MODULAR_DEPRECATION_ARG } from '@react-native-firebase/app/dist/module/common'; - -class QueryConstraint { - constructor(type, ...args) { - this._type = type; - this._args = args; - } - - _apply(query) { - return query[this._type].apply(query, [...this._args, MODULAR_DEPRECATION_ARG]); - } -} - -/** - * @param {number | string | boolean | null} value - * @param {string?} key - * @returns {QueryConstraint} - */ -export function endAt(value, key) { - return new QueryConstraint('endAt', value, key); -} - -/** - * @param {number | string | boolean | null} value - * @param {string?} key - * @returns {QueryConstraint} - */ -export function endBefore(value, key) { - return new QueryConstraint('endBefore', value, key); -} - -/** - * @param {number | string | boolean | null} value, - * @param {string?} key, - * @returns {QueryConstraint} - */ -export function startAt(value, key) { - return new QueryConstraint('startAt', value, key); -} - -/** - * @param {number | string | boolean | null} value, - * @param {string?} key, - * @returns {QueryConstraint} - */ -export function startAfter(value, key) { - return new QueryConstraint('startAfter', value, key); -} - -/** - * @param {number} limit - * @returns {QueryConstraint} - */ -export function limitToFirst(limit) { - return new QueryConstraint('limitToFirst', limit); -} - -/** - * @param {number} limit - * @returns {QueryConstraint} - */ -export function limitToLast(limit) { - return new QueryConstraint('limitToLast', limit); -} - -/** - * @param {string} path - * @returns {QueryConstraint} - */ -export function orderByChild(path) { - return new QueryConstraint('orderByChild', path); -} - -export function orderByKey() { - return new QueryConstraint('orderByKey'); -} - -export function orderByPriority() { - return new QueryConstraint('orderByPriority'); -} - -export function orderByValue() { - return new QueryConstraint('orderByValue'); -} - -/** - * @param {number | string | boolean | null} value - * @param {string?} key - * @returns {QueryConstraint} - */ -export function equalTo(value, key) { - return new QueryConstraint('equalTo', value, key); -} - -/** - * @param {Query} query - * @param {QueryConstraint[]} queryConstraints - * @returns {Query} - */ -export function query(query, ...queryConstraints) { - let q = query; - for (const queryConstraint of queryConstraints) { - q = queryConstraint._apply(q); - } - return q; -} - -/** - * @param {Query} query - * @param {EventType} eventType - * @param {(snapshot: DataSnapshot) => unknown} callback - * @param {((error: Error) => unknown) | ListenOptions} cancelCallbackOrListenOptions - * @param {ListenOptions?} options - * @returns {Unsubscribe} - */ -function addEventListener(query, eventType, callback, cancelCallbackOrListenOptions, options) { - let cancelCallback = cancelCallbackOrListenOptions; - - if (typeof cancelCallbackOrListenOptions === 'object') { - cancelCallback = undefined; - options = cancelCallbackOrListenOptions; - } - - if (options && options.onlyOnce) { - const userCallback = callback; - callback = snapshot => { - query.off.call(query, eventType, callback, null, MODULAR_DEPRECATION_ARG); - return userCallback(snapshot); - }; - } - - query.on.call(query, eventType, callback, cancelCallback, null, MODULAR_DEPRECATION_ARG); - - return () => query.off.call(query, eventType, callback, null, MODULAR_DEPRECATION_ARG); -} - -/** - * @param {Query} query - * @param {(snapshot: DataSnapshot) => unknown} callback - * @param {((error: Error) => unknown) | ListenOptions | undefined} cancelCallbackOrListenOptions - * @param {ListenOptions?} options - * @returns {Unsubscribe} - */ -export function onValue(query, callback, cancelCallbackOrListenOptions, options) { - return addEventListener(query, 'value', callback, cancelCallbackOrListenOptions, options); -} - -/** - * @param {Query} query - * @param {(snapshot: DataSnapshot, previousChildName: string | null) => unknown} callback - * @param {((error: Error) => unknown) | ListenOptions | undefined} cancelCallbackOrListenOptions - * @param {ListenOptions?} options - * @returns {Unsubscribe} - */ -export function onChildAdded(query, callback, cancelCallbackOrListenOptions, options) { - return addEventListener(query, 'child_added', callback, cancelCallbackOrListenOptions, options); -} - -/** - * @param {Query} query - * @param {(snapshot: DataSnapshot, previousChildName: string | null) => unknown} callback - * @param {((error: Error) => unknown) | ListenOptions | undefined} cancelCallbackOrListenOptions - * @param {ListenOptions?} options - * @returns {Unsubscribe} - */ -export function onChildChanged(query, callback, cancelCallbackOrListenOptions, options) { - return addEventListener(query, 'child_changed', callback, cancelCallbackOrListenOptions, options); -} - -/** - * @param {Query} query - * @param {(snapshot: DataSnapshot, previousChildName: string | null) => unknown} callback - * @param {((error: Error) => unknown) | ListenOptions | undefined} cancelCallbackOrListenOptions - * @param {ListenOptions?} options - * @returns {Unsubscribe} - */ -export function onChildMoved(query, callback, cancelCallbackOrListenOptions, options) { - return addEventListener(query, 'child_moved', callback, cancelCallbackOrListenOptions, options); -} - -/** - * @param {Query} query - * @param {(snapshot: DataSnapshot, previousChildName: string | null) => unknown} callback - * @param {((error: Error) => unknown) | ListenOptions | undefined} cancelCallbackOrListenOptions - * @param {ListenOptions?} options - * @returns {Unsubscribe} - */ -export function onChildRemoved(query, callback, cancelCallbackOrListenOptions, options) { - return addEventListener(query, 'child_removed', callback, cancelCallbackOrListenOptions, options); -} - -/** - * @param {DatabaseReference} ref - * @param {unknown} value - * @returns {Promise} - */ -export function set(ref, value) { - return ref.set.call(ref, value, () => {}, MODULAR_DEPRECATION_ARG); -} - -/** - * @param {DatabaseReference} ref - * @param {string | number | null} priority - * @returns {Promise} - */ -export function setPriority(ref, priority) { - return ref.setPriority.call(ref, priority, () => {}, MODULAR_DEPRECATION_ARG); -} - -/** - * @param {DatabaseReference} ref - * @param {unknown} value - * @param {string | number | null} priority - * @returns {Promise} - */ -export function setWithPriority(ref, value, priority) { - return ref.setWithPriority.call(ref, value, priority, () => {}, MODULAR_DEPRECATION_ARG); -} - -/** - * @param {Query} query - * @returns {DataSnapshot} - */ -export function get(query) { - return query.once.call( - query, - 'value', - () => {}, - () => {}, - {}, - MODULAR_DEPRECATION_ARG, - ); -} - -export function off(_query, _eventType, _callback) { - throw new Error('off() is not implemented - use unsubscriber callback returned when subscribing'); -} - -/** - * @param {DatabaseReference} parent - * @param {string} path - * @returns {DatabaseReference} - */ -export function child(parent, path) { - return parent.child.call(parent, path, MODULAR_DEPRECATION_ARG); -} - -/** - * @param {DatabaseReference} ref - * @returns {OnDisconnect} - */ -export function onDisconnect(ref) { - return ref.onDisconnect.call(ref, MODULAR_DEPRECATION_ARG); -} - -/** - * @param {DatabaseReference} ref - * @param {boolean} value - * @returns {Promise} - */ -export function keepSynced(ref, value) { - return ref.keepSynced.call(ref, value, MODULAR_DEPRECATION_ARG); -} - -/** - * @param {DatabaseReference} parent - * @param {unknown} value - * @returns {ThenableReference} - */ -export function push(parent, value) { - return parent.push.call(parent, value, MODULAR_DEPRECATION_ARG); -} - -/** - * @param {DatabaseReference} ref - * @returns {Promise} - */ -export function remove(ref) { - return ref.remove.call(ref, MODULAR_DEPRECATION_ARG); -} - -/** - * @param {DatabaseReference} ref - * @param {object} values - * @returns {Promise} - */ -export function update(ref, values) { - return ref.update.call(ref, values, MODULAR_DEPRECATION_ARG); -} diff --git a/packages/database/lib/modular/query.ts b/packages/database/lib/modular/query.ts new file mode 100644 index 0000000000..71b581af20 --- /dev/null +++ b/packages/database/lib/modular/query.ts @@ -0,0 +1,249 @@ +/* + * Copyright (c) 2016-present Invertase Limited & Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this library 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 { MODULAR_DEPRECATION_ARG } from '@react-native-firebase/app/dist/module/common'; +import type { + Query, + DataSnapshot, + Reference, + OnDisconnect, + EventType, + ThenableReference, +} from '../types/database'; + +export type Unsubscribe = () => void; + +export interface ListenOptions { + readonly onlyOnce?: boolean; +} + +export type QueryConstraintType = + | 'endAt' + | 'endBefore' + | 'startAt' + | 'startAfter' + | 'limitToFirst' + | 'limitToLast' + | 'orderByChild' + | 'orderByKey' + | 'orderByPriority' + | 'orderByValue' + | 'equalTo'; + +export interface QueryConstraint { + readonly _type: QueryConstraintType; + _apply(query: Query): Query; +} + +class QueryConstraintImpl implements QueryConstraint { + readonly _type: QueryConstraintType; + private readonly _args: unknown[]; + + constructor(type: QueryConstraintType, ...args: unknown[]) { + this._type = type; + this._args = args; + } + + _apply(query: Query): Query { + return ((query as any)[this._type] as (...args: unknown[]) => Query).apply(query, [ + ...this._args, + MODULAR_DEPRECATION_ARG, + ]); + } +} + +export function endAt(value: number | string | boolean | null, key?: string): QueryConstraint { + return new QueryConstraintImpl('endAt', value, key); +} + +export function endBefore(value: number | string | boolean | null, key?: string): QueryConstraint { + return new QueryConstraintImpl('endBefore', value, key); +} + +export function startAt(value?: number | string | boolean | null, key?: string): QueryConstraint { + return new QueryConstraintImpl('startAt', value, key); +} + +export function startAfter(value: number | string | boolean | null, key?: string): QueryConstraint { + return new QueryConstraintImpl('startAfter', value, key); +} + +export function limitToFirst(limit: number): QueryConstraint { + return new QueryConstraintImpl('limitToFirst', limit); +} + +export function limitToLast(limit: number): QueryConstraint { + return new QueryConstraintImpl('limitToLast', limit); +} + +export function orderByChild(path: string): QueryConstraint { + return new QueryConstraintImpl('orderByChild', path); +} + +export function orderByKey(): QueryConstraint { + return new QueryConstraintImpl('orderByKey'); +} + +export function orderByPriority(): QueryConstraint { + return new QueryConstraintImpl('orderByPriority'); +} + +export function orderByValue(): QueryConstraint { + return new QueryConstraintImpl('orderByValue'); +} + +export function equalTo(value: number | string | boolean | null, key?: string): QueryConstraint { + return new QueryConstraintImpl('equalTo', value, key); +} + +export function query(query: Query, ...queryConstraints: QueryConstraint[]): Query { + let q = query; + for (const queryConstraint of queryConstraints) { + q = queryConstraint._apply(q); + } + return q; +} + +function addEventListener( + query: Query, + eventType: EventType, + callback: (snapshot: DataSnapshot, previousChildKey?: string | null) => unknown, + cancelCallbackOrListenOptions?: ((error: Error) => unknown) | ListenOptions, + options?: ListenOptions, +): Unsubscribe { + let cancelCallback: ((error: Error) => unknown) | undefined; + let listenOptions: ListenOptions | undefined = options; + + if (typeof cancelCallbackOrListenOptions === 'object') { + cancelCallback = undefined; + listenOptions = cancelCallbackOrListenOptions; + } else { + cancelCallback = cancelCallbackOrListenOptions; + } + + if (listenOptions && listenOptions.onlyOnce) { + const userCallback = callback; + callback = (snapshot: DataSnapshot, previousChildKey?: string | null) => { + (query.off as any).call(query, eventType, callback, null, MODULAR_DEPRECATION_ARG); + return userCallback(snapshot, previousChildKey); + }; + } + + (query.on as any).call(query, eventType, callback, cancelCallback, null, MODULAR_DEPRECATION_ARG); + + return () => (query.off as any).call(query, eventType, callback, null, MODULAR_DEPRECATION_ARG); +} + +export function onValue( + query: Query, + callback: (snapshot: DataSnapshot) => unknown, + cancelCallbackOrListenOptions?: ((error: Error) => unknown) | ListenOptions, + options?: ListenOptions, +): Unsubscribe { + return addEventListener(query, 'value', callback, cancelCallbackOrListenOptions, options); +} + +export function onChildAdded( + query: Query, + callback: (snapshot: DataSnapshot, previousChildName?: string | null) => unknown, + cancelCallbackOrListenOptions?: ((error: Error) => unknown) | ListenOptions, + options?: ListenOptions, +): Unsubscribe { + return addEventListener(query, 'child_added', callback, cancelCallbackOrListenOptions, options); +} + +export function onChildChanged( + query: Query, + callback: (snapshot: DataSnapshot, previousChildName?: string | null) => unknown, + cancelCallbackOrListenOptions?: ((error: Error) => unknown) | ListenOptions, + options?: ListenOptions, +): Unsubscribe { + return addEventListener(query, 'child_changed', callback, cancelCallbackOrListenOptions, options); +} + +export function onChildMoved( + query: Query, + callback: (snapshot: DataSnapshot, previousChildName?: string | null) => unknown, + cancelCallbackOrListenOptions?: ((error: Error) => unknown) | ListenOptions, + options?: ListenOptions, +): Unsubscribe { + return addEventListener(query, 'child_moved', callback, cancelCallbackOrListenOptions, options); +} + +export function onChildRemoved( + query: Query, + callback: (snapshot: DataSnapshot) => unknown, + cancelCallbackOrListenOptions?: ((error: Error) => unknown) | ListenOptions, + options?: ListenOptions, +): Unsubscribe { + return addEventListener(query, 'child_removed', callback, cancelCallbackOrListenOptions, options); +} + +export function set(ref: Reference, value: unknown): Promise { + return (ref.set as any).call(ref, value, () => {}, MODULAR_DEPRECATION_ARG); +} + +export function setPriority(ref: Reference, priority: string | number | null): Promise { + return (ref.setPriority as any).call(ref, priority, () => {}, MODULAR_DEPRECATION_ARG); +} + +export function setWithPriority( + ref: Reference, + value: unknown, + priority: string | number | null, +): Promise { + return (ref.setWithPriority as any).call(ref, value, priority, () => {}, MODULAR_DEPRECATION_ARG); +} + +export function get(query: Query): Promise { + return (query.once as any).call( + query, + 'value', + () => {}, + () => {}, + {}, + MODULAR_DEPRECATION_ARG, + ); +} + +export function off(_query: Query, _eventType?: EventType, _callback?: unknown): void { + throw new Error('off() is not implemented - use unsubscriber callback returned when subscribing'); +} + +export function child(parent: Reference, path: string): Reference { + return (parent.child as any).call(parent, path, MODULAR_DEPRECATION_ARG); +} + +export function onDisconnect(ref: Reference): OnDisconnect { + return (ref.onDisconnect as any).call(ref, MODULAR_DEPRECATION_ARG); +} + +export function keepSynced(ref: Reference, value: boolean): Promise { + return (ref.keepSynced as any).call(ref, value, MODULAR_DEPRECATION_ARG); +} + +export function push(parent: Reference, value?: unknown): ThenableReference { + return (parent.push as any).call(parent, value, MODULAR_DEPRECATION_ARG); +} + +export function remove(ref: Reference): Promise { + return (ref.remove as any).call(ref, MODULAR_DEPRECATION_ARG); +} + +export function update(ref: Reference, values: { [key: string]: unknown }): Promise { + return (ref.update as any).call(ref, values, MODULAR_DEPRECATION_ARG); +} diff --git a/packages/database/lib/modular/transaction.d.ts b/packages/database/lib/modular/transaction.d.ts deleted file mode 100644 index a453db3dcb..0000000000 --- a/packages/database/lib/modular/transaction.d.ts +++ /dev/null @@ -1,54 +0,0 @@ -import { FirebaseDatabaseTypes } from '../..'; - -export type TransactionResult = FirebaseDatabaseTypes.TransactionResult; -import DatabaseReference = FirebaseDatabaseTypes.Reference; - -/** - * An options object to configure transactions. - */ -export interface TransactionOptions { - readonly applyLocally?: boolean; -} - -/** - * Atomically modifies the data at this location. - * - * Atomically modify the data at this location. Unlike a normal `set()`, which - * just overwrites the data regardless of its previous value, `runTransaction()` is - * used to modify the existing value to a new value, ensuring there are no - * conflicts with other clients writing to the same location at the same time. - * - * To accomplish this, you pass `runTransaction()` an update function which is - * used to transform the current value into a new value. If another client - * writes to the location before your new value is successfully written, your - * update function will be called again with the new current value, and the - * write will be retried. This will happen repeatedly until your write succeeds - * without conflict or you abort the transaction by not returning a value from - * your update function. - * - * Note: Modifying data with `set()` will cancel any pending transactions at - * that location, so extreme care should be taken if mixing `set()` and - * `runTransaction()` to update the same data. - * - * Note: When using transactions with Security and Firebase Rules in place, be - * aware that a client needs `.read` access in addition to `.write` access in - * order to perform a transaction. This is because the client-side nature of - * transactions requires the client to read the data in order to transactionally - * update it. - * - * @param ref - The location to atomically modify. - * @param transactionUpdate - A developer-supplied function which will be passed - * the current data stored at this location (as a JavaScript object). The - * function should return the new value it would like written (as a JavaScript - * object). If `undefined` is returned (i.e. you return with no arguments) the - * transaction will be aborted and the data at this location will not be - * modified. - * @param options - An options object to configure transactions. - * @returns A `Promise` that can optionally be used instead of the `onComplete` - * callback to handle success and failure. - */ -export function runTransaction( - ref: DatabaseReference, - transactionUpdate: (currentData: any) => unknown, - options?: TransactionOptions, -): Promise; diff --git a/packages/database/lib/modular/transaction.js b/packages/database/lib/modular/transaction.js deleted file mode 100644 index 257ec66a3b..0000000000 --- a/packages/database/lib/modular/transaction.js +++ /dev/null @@ -1,24 +0,0 @@ -/** - * @typedef {import('./database').DatabaseReference} DatabaseReference - * @typedef {import('./transaction').TransactionOptions} TransactionOptions - * @typedef {import('./transaction').TransactionResult} TransactionResult - */ - -/** - * @param {DatabaseReference} ref - * @param {(options: any) => unknown} transactionUpdate - * @param {TransactionOptions?} options - * @returns {Promise} - */ - -import { MODULAR_DEPRECATION_ARG } from '@react-native-firebase/app/dist/module/common'; - -export function runTransaction(ref, transactionUpdate, options) { - return ref.transaction.call( - ref, - transactionUpdate, - undefined, - options && options.applyLocally, - MODULAR_DEPRECATION_ARG, - ); -} diff --git a/packages/database/lib/modular/transaction.ts b/packages/database/lib/modular/transaction.ts new file mode 100644 index 0000000000..25259569a6 --- /dev/null +++ b/packages/database/lib/modular/transaction.ts @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2016-present Invertase Limited & Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this library 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 { MODULAR_DEPRECATION_ARG } from '@react-native-firebase/app/dist/module/common'; +import type { Reference, TransactionResult } from '../types/database'; + +export interface TransactionOptions { + readonly applyLocally?: boolean; +} + +/** + * Atomically modifies the data at this location. + * @param ref - The location to atomically modify. + * @param transactionUpdate - A developer-supplied function which will be passed the current data. + * @param options - An options object to configure transactions. + * @returns {Promise} + */ +export function runTransaction( + ref: Reference, + transactionUpdate: (currentData: unknown) => unknown, + options?: TransactionOptions, +): Promise { + return (ref.transaction as any).call( + ref, + transactionUpdate, + undefined, + options && options.applyLocally, + MODULAR_DEPRECATION_ARG, + ); +} diff --git a/packages/database/lib/index.js b/packages/database/lib/namespaced.ts similarity index 67% rename from packages/database/lib/index.js rename to packages/database/lib/namespaced.ts index c9424fa5fe..e7d3344a61 100644 --- a/packages/database/lib/index.js +++ b/packages/database/lib/namespaced.ts @@ -26,15 +26,17 @@ import { createModuleNamespace, FirebaseModule, getFirebaseRoot, + type ModuleConfig, } from '@react-native-firebase/app/dist/module/internal'; import { setReactNativeModule } from '@react-native-firebase/app/dist/module/internal/nativeModule'; +import { createDeprecationProxy } from '@react-native-firebase/app/dist/module/common'; +import type { ReactNativeFirebase } from '@react-native-firebase/app'; import DatabaseReference from './DatabaseReference'; import DatabaseStatics from './DatabaseStatics'; import DatabaseTransaction from './DatabaseTransaction'; -import version from './version'; +import { version } from './version'; import fallBackModule from './web/RNFBDatabaseModule'; - -import { createDeprecationProxy } from '@react-native-firebase/app/dist/module/common'; +import type { FirebaseDatabaseTypes } from './types/namespaced'; const namespace = 'database'; @@ -46,46 +48,40 @@ const nativeModuleName = [ 'RNFBDatabaseTransactionModule', ]; -class FirebaseDatabaseModule extends FirebaseModule { - constructor(app, config, databaseUrl) { - super(app, config, databaseUrl); +class FirebaseDatabaseModule extends FirebaseModule { + _serverTimeOffset: number; + _customUrlOrRegion: string; + _transaction: DatabaseTransaction; + + constructor( + app: ReactNativeFirebase.FirebaseAppBase, + config: ModuleConfig, + databaseUrl?: string | null, + ) { + super(app, config, databaseUrl ?? undefined); this._serverTimeOffset = 0; - this._customUrlOrRegion = databaseUrl || this.app.options.databaseURL; - this._transaction = new DatabaseTransaction(this); + this._customUrlOrRegion = databaseUrl || this.app.options.databaseURL || ''; + this._transaction = new DatabaseTransaction(this as any); setTimeout(() => { this._syncServerTimeOffset(); }, 100); } - /** - * Keep the server time offset in sync with the server time - * @private - */ - _syncServerTimeOffset() { - this.ref('.info/serverTimeOffset').on( + _syncServerTimeOffset(): void { + (this.ref('.info/serverTimeOffset').on as any)( 'value', - snapshot => { - this._serverTimeOffset = snapshot.val(); + (snapshot: any) => { + this._serverTimeOffset = snapshot.val() as number; }, MODULAR_DEPRECATION_ARG, ); } - /** - * - * @returns {Date} - * @private - */ - getServerTime() { + getServerTime(): Date { return new Date(Date.now() + this._serverTimeOffset); } - /** - * Returns a new Reference instance from a given path. Defaults to the root reference. - * @param path - * @returns {DatabaseReference} - */ - ref(path = '/') { + ref(path: string = '/'): DatabaseReference { if (!isString(path)) { throw new Error("firebase.app().database().ref(*) 'path' must be a string value."); } @@ -96,17 +92,10 @@ class FirebaseDatabaseModule extends FirebaseModule { ); } - return createDeprecationProxy(new DatabaseReference(this, path)); + return createDeprecationProxy(new DatabaseReference(this as any, path)) as DatabaseReference; } - /** - * Generates a Reference from a database URL. - * Note domain must be the same. - * Any query parameters are stripped as per the web SDK. - * @param url - * @returns {DatabaseReference} - */ - refFromURL(url) { + refFromURL(url: string): DatabaseReference { if (!isString(url) || !url.startsWith('https://')) { throw new Error( "firebase.app().database().refFromURL(*) 'url' must be a valid database URL.", @@ -124,56 +113,40 @@ class FirebaseDatabaseModule extends FirebaseModule { path = path.slice(0, path.indexOf('?')); } - return createDeprecationProxy(new DatabaseReference(this, path || '/')); + return createDeprecationProxy( + new DatabaseReference(this as any, path || '/'), + ) as DatabaseReference; } - /** - * goOnline - */ - goOnline() { - return this.native.goOnline(); + goOnline(): Promise { + return (this.native as any).goOnline(); } - /** - * goOffline - */ - goOffline() { - return this.native.goOffline(); + goOffline(): Promise { + return (this.native as any).goOffline(); } - /** - * - * @param enabled - */ - setPersistenceEnabled(enabled) { + setPersistenceEnabled(enabled: boolean): Promise { if (!isBoolean(enabled)) { throw new Error( "firebase.app().database().setPersistenceEnabled(*) 'enabled' must be a boolean value.", ); } - return this.native.setPersistenceEnabled(enabled); + return (this.native as any).setPersistenceEnabled(enabled); } - /** - * - * @param enabled - */ - setLoggingEnabled(enabled) { + setLoggingEnabled(enabled: boolean): Promise { if (!isBoolean(enabled)) { throw new Error( "firebase.app().database().setLoggingEnabled(*) 'enabled' must be a boolean value.", ); } - return this.native.setLoggingEnabled(enabled); + return (this.native as any).setLoggingEnabled(enabled); } - /** - * - * @param bytes - */ - setPersistenceCacheSizeBytes(bytes) { + setPersistenceCacheSizeBytes(bytes: number): Promise { if (!isNumber(bytes)) { throw new Error( "firebase.app().database().setPersistenceCacheSizeBytes(*) 'bytes' must be a number value.", @@ -192,10 +165,10 @@ class FirebaseDatabaseModule extends FirebaseModule { ); } - return this.native.setPersistenceCacheSizeBytes(bytes); + return (this.native as any).setPersistenceCacheSizeBytes(bytes); } - useEmulator(host, port) { + useEmulator(host: string, port: number): [string, number] { if (!host || !isString(host) || !port || !isNumber(port)) { throw new Error('firebase.database().useEmulator() takes a non-empty host and port'); } @@ -219,17 +192,15 @@ class FirebaseDatabaseModule extends FirebaseModule { ); } } - this.native.useEmulator(_host, port); - return [_host, port]; // undocumented return, just used to unit test android host remapping + (this.native as any).useEmulator(_host, port); + // @ts-ignore undocumented return, just used to unit test android host remapping + return [_host, port]; } } -// import { SDK_VERSION } from '@react-native-firebase/database'; export const SDK_VERSION = version; -// import database from '@react-native-firebase/database'; -// database().X(...); -export default createModuleNamespace({ +const databaseNamespace = createModuleNamespace({ statics: DatabaseStatics, version, namespace, @@ -240,13 +211,33 @@ export default createModuleNamespace({ ModuleClass: FirebaseDatabaseModule, }); -export * from './modular'; +type DatabaseNamespace = ReactNativeFirebase.FirebaseModuleWithStaticsAndApp< + FirebaseDatabaseTypes.Module, + FirebaseDatabaseTypes.Statics +> & { + database: ReactNativeFirebase.FirebaseModuleWithStaticsAndApp< + FirebaseDatabaseTypes.Module, + FirebaseDatabaseTypes.Statics + >; + firebase: ReactNativeFirebase.Module; + app(name?: string): ReactNativeFirebase.FirebaseApp; +}; + +// import database from '@react-native-firebase/database'; +// database().X(...); +export default databaseNamespace as unknown as DatabaseNamespace; // import database, { firebase } from '@react-native-firebase/database'; // database().X(...); // firebase.database().X(...); -export const firebase = getFirebaseRoot(); +export const firebase = + getFirebaseRoot() as unknown as ReactNativeFirebase.FirebaseNamespacedExport< + 'database', + FirebaseDatabaseTypes.Module, + FirebaseDatabaseTypes.Statics, + true + >; for (let i = 0; i < nativeModuleName.length; i++) { - setReactNativeModule(nativeModuleName[i], fallBackModule); + setReactNativeModule(nativeModuleName[i]!, fallBackModule); } diff --git a/packages/database/lib/types/database.ts b/packages/database/lib/types/database.ts new file mode 100644 index 0000000000..a74a65ac87 --- /dev/null +++ b/packages/database/lib/types/database.ts @@ -0,0 +1,158 @@ +/* + * Copyright (c) 2016-present Invertase Limited & Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this library 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 type { ReactNativeFirebase } from '@react-native-firebase/app'; + +/** + * Database module instance (public/modular API). Only `app` is exposed on the type. + */ +export interface Database { + /** The FirebaseApp this module is associated with */ + app: ReactNativeFirebase.FirebaseApp; +} + +/** + * Server value placeholders (timestamp, increment). + */ +export interface ServerValue { + TIMESTAMP: object; + increment(delta: number): object; +} + +/** + * Realtime Database statics (ServerValue, SDK_VERSION). + */ +export interface Statics { + ServerValue: ServerValue; + SDK_VERSION: string; +} + +export type EventType = 'value' | 'child_added' | 'child_changed' | 'child_moved' | 'child_removed'; + +/** + * Result of a transaction. + */ +export interface TransactionResult { + committed: boolean; + snapshot: DataSnapshot; +} + +/** + * DataSnapshot from a Database location (read-only view of data). + */ +export interface DataSnapshot { + key: string | null; + ref: Reference; + child(path: string): DataSnapshot; + exists(): boolean; + exportVal(): unknown; + forEach(action: (child: DataSnapshot) => true | undefined): boolean; + getPriority(): string | number | null; + hasChild(path: string): boolean; + hasChildren(): boolean; + numChildren(): number; + toJSON(): object | null; + val(): unknown; +} + +/** + * Query sorts and filters data at a Database location. + */ +export interface Query { + ref: Reference; + endAt(value: number | string | boolean | null, key?: string): Query; + equalTo(value: number | string | boolean | null, key?: string): Query; + isEqual(other: Query): boolean; + limitToFirst(limit: number): Query; + limitToLast(limit: number): Query; + off( + eventType?: EventType, + callback?: (a: DataSnapshot, b?: string | null) => void, + context?: Record, + ): void; + on( + eventType: EventType, + callback: (data: DataSnapshot, previousChildKey?: string | null) => void, + cancelCallbackOrContext?: ((a: Error) => void) | Record | null, + context?: Record | null, + ): (a: DataSnapshot | null, b?: string | null) => void; + once( + eventType: EventType, + successCallback?: (a: DataSnapshot, b?: string | null) => unknown, + failureCallbackContext?: ((a: Error) => void) | Record | null, + ): Promise; + orderByChild(path: string): Query; + orderByKey(): Query; + orderByPriority(): Query; + orderByValue(): Query; + startAt(value: number | string | boolean | null, key?: string): Query; + toJSON(): object; + toString(): string; + keepSynced(bool: boolean): Promise; +} + +/** + * Reference to a specific location in the Database. + */ +export interface Reference extends Query { + parent: Reference | null; + root: Reference; + key: string | null; + child(path: string): Reference; + set(value: unknown, onComplete?: (error: Error | null) => void): Promise; + update( + values: { [key: string]: unknown }, + onComplete?: (error: Error | null) => void, + ): Promise; + setPriority( + priority: string | number | null, + onComplete?: (error: Error | null) => void, + ): Promise; + setWithPriority( + newVal: unknown, + newPriority: string | number | null, + onComplete?: (error: Error | null) => void, + ): Promise; + remove(onComplete?: (error: Error | null) => void): Promise; + transaction( + transactionUpdate: (currentData: unknown) => unknown | undefined, + onComplete?: (error: Error | null, committed: boolean, finalResult: DataSnapshot) => void, + applyLocally?: boolean, + ): Promise; + push(value?: unknown, onComplete?: () => void): ThenableReference; + onDisconnect(): OnDisconnect; +} + +export type ThenableReference = Reference; + +/** + * OnDisconnect: write or clear data when the client disconnects. + */ +export interface OnDisconnect { + cancel(onComplete?: (error: Error | null) => void): Promise; + remove(onComplete?: (error: Error | null) => void): Promise; + set(value: unknown, onComplete?: (error: Error | null) => void): Promise; + setWithPriority( + value: unknown, + priority: string | number | null, + onComplete?: (error: Error | null) => void, + ): Promise; + update( + values: { [key: string]: unknown }, + onComplete?: (error: Error | null) => void, + ): Promise; +} diff --git a/packages/database/lib/types/internal.ts b/packages/database/lib/types/internal.ts new file mode 100644 index 0000000000..82e4ded367 --- /dev/null +++ b/packages/database/lib/types/internal.ts @@ -0,0 +1,111 @@ +/* + * Copyright (c) 2016-present Invertase Limited & Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this library 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 type { Database, Reference } from './database'; + +/** + * Wrapped native module interface for the main Database module. + * React Native Firebase merges multiple native modules (RNFBDatabaseModule, etc.) into one; + * this interface describes the main module methods used by the JS class. + */ +export interface RNFBDatabaseModule { + goOnline(): Promise; + goOffline(): Promise; + setPersistenceEnabled(enabled: boolean): Promise; + setLoggingEnabled(enabled: boolean): Promise; + setPersistenceCacheSizeBytes(bytes: number): Promise; + useEmulator(host: string, port: number): void; + set(path: string, data: { value: unknown }): Promise; + update(path: string, data: { values: { [key: string]: unknown } }): Promise; + setWithPriority( + path: string, + data: { value: unknown; priority: string | number | null }, + ): Promise; + remove(path: string): Promise; + setPriority(path: string, data: { priority: string | number | null }): Promise; + onDisconnectCancel(path: string): Promise; + onDisconnectRemove(path: string): Promise; + onDisconnectSet(path: string, data: { value: unknown }): Promise; + onDisconnectSetWithPriority( + path: string, + data: { value: unknown; priority: string | number | null }, + ): Promise; + onDisconnectUpdate(path: string, data: { values: { [key: string]: unknown } }): Promise; + on(props: { + eventType: string; + path: string; + key: string; + modifiers: unknown[]; + hasCancellationCallback: boolean; + registration: { + eventRegistrationKey: string; + key?: string; + registrationCancellationKey?: string; + }; + }): Promise; + once( + path: string, + modifiers: unknown[], + eventType: string, + ): Promise<{ + snapshot?: unknown; + previousChildName?: string | null; + value?: unknown; + key?: string | null; + exists?: boolean; + childKeys?: string[]; + priority?: string | number | null; + }>; + keepSynced(queryKey: string, path: string, modifiers: unknown[], enabled: boolean): Promise; + transactionStart( + path: string, + id: number, + applyLocally: boolean, + transactionUpdater?: (currentData: unknown) => unknown, + ): void; + transactionTryCommit(id: number, updates: { [key: string]: unknown }): void; +} + +/** + * Internal Database type with native module and methods used by modular .call() pattern. + */ +export interface DatabaseInternal extends Database { + native: RNFBDatabaseModule; + _serverTimeOffset: number; + _customUrlOrRegion: string; + _transaction: import('../DatabaseTransaction').default; + emitter: typeof import('@react-native-firebase/app/dist/module/internal/SharedEventEmitter').default; + getServerTime(): Date; + ref(path?: string): Reference; + refFromURL(url: string): Reference; + goOnline(): Promise; + goOffline(): Promise; + setPersistenceEnabled(enabled: boolean): void; + setLoggingEnabled(enabled: boolean): void; + setPersistenceCacheSizeBytes(bytes: number): void; + useEmulator(host: string, port: number): void; + eventNameForApp(...args: Array): string; +} + +/** + * Type helper for methods called with MODULAR_DEPRECATION_ARG via .call(). + */ +export type WithModularDeprecationArg unknown> = T extends ( + ...args: infer A +) => infer R + ? (...args: [...A, unknown?]) => R + : never; diff --git a/packages/database/lib/types/namespaced.ts b/packages/database/lib/types/namespaced.ts new file mode 100644 index 0000000000..4eb7249696 --- /dev/null +++ b/packages/database/lib/types/namespaced.ts @@ -0,0 +1,60 @@ +/* + * Copyright (c) 2016-present Invertase Limited & Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this library 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 type { ReactNativeFirebase } from '@react-native-firebase/app'; + +/** + * @deprecated Use modular API (getDatabase, ref, set, onValue, etc.) and types from '@react-native-firebase/database' instead. + */ +/* eslint-disable @typescript-eslint/no-namespace */ +export namespace FirebaseDatabaseTypes { + type FirebaseModule = ReactNativeFirebase.FirebaseModule; + + export type DataSnapshot = import('./database').DataSnapshot; + export type EventType = import('./database').EventType; + export type OnDisconnect = import('./database').OnDisconnect; + export type Query = import('./database').Query; + export type Reference = import('./database').Reference; + export type ServerValue = import('./database').ServerValue; + export type ThenableReference = import('./database').ThenableReference; + export type TransactionResult = import('./database').TransactionResult; + + /** + * @deprecated Use Statics from modular API (ServerValue, increment, serverTimestamp) instead. + */ + export interface Statics { + ServerValue: ServerValue; + SDK_VERSION: string; + } + + /** + * @deprecated Use getDatabase() and modular functions instead. + */ + export interface Module extends FirebaseModule { + app: ReactNativeFirebase.FirebaseApp; + getServerTime(): Date; + ref(path?: string): Reference; + refFromURL(url: string): Reference; + goOnline(): Promise; + goOffline(): Promise; + setPersistenceEnabled(enabled: boolean): void; + setLoggingEnabled(enabled: boolean): void; + setPersistenceCacheSizeBytes(bytes: number): void; + useEmulator(host: string, port: number): void; + } +} +/* eslint-enable @typescript-eslint/no-namespace */ diff --git a/packages/database/lib/web/RNFBDatabaseModule.android.js b/packages/database/lib/web/RNFBDatabaseModule.android.js deleted file mode 100644 index af77c859b1..0000000000 --- a/packages/database/lib/web/RNFBDatabaseModule.android.js +++ /dev/null @@ -1,2 +0,0 @@ -// No-op for android. -export default {}; diff --git a/packages/database/lib/web/RNFBDatabaseModule.android.ts b/packages/database/lib/web/RNFBDatabaseModule.android.ts new file mode 100644 index 0000000000..91c2b381b6 --- /dev/null +++ b/packages/database/lib/web/RNFBDatabaseModule.android.ts @@ -0,0 +1,3 @@ +// No-op for android. +const RNFBDatabaseModule = {}; +export default RNFBDatabaseModule; diff --git a/packages/database/lib/web/RNFBDatabaseModule.ios.js b/packages/database/lib/web/RNFBDatabaseModule.ios.js deleted file mode 100644 index a3429ada0e..0000000000 --- a/packages/database/lib/web/RNFBDatabaseModule.ios.js +++ /dev/null @@ -1,2 +0,0 @@ -// No-op for ios. -export default {}; diff --git a/packages/database/lib/web/RNFBDatabaseModule.ios.ts b/packages/database/lib/web/RNFBDatabaseModule.ios.ts new file mode 100644 index 0000000000..f95b0bd115 --- /dev/null +++ b/packages/database/lib/web/RNFBDatabaseModule.ios.ts @@ -0,0 +1,3 @@ +// Re-export the main module +const RNFBDatabaseModule = {}; +export default RNFBDatabaseModule; diff --git a/packages/database/lib/web/RNFBDatabaseModule.js b/packages/database/lib/web/RNFBDatabaseModule.js deleted file mode 100644 index 3122098e56..0000000000 --- a/packages/database/lib/web/RNFBDatabaseModule.js +++ /dev/null @@ -1,558 +0,0 @@ -import { - getApp, - getDatabase, - connectDatabaseEmulator, - enableLogging, - goOnline, - goOffline, - ref, - set, - update, - setWithPriority, - remove, - setPriority, - onDisconnect, - onValue, - onChildAdded, - onChildChanged, - onChildMoved, - onChildRemoved, - runTransaction, -} from '@react-native-firebase/app/dist/module/internal/web/firebaseDatabase'; -import { - guard, - getWebError, - emitEvent, -} from '@react-native-firebase/app/dist/module/internal/web/utils'; -import { getQueryInstance } from './query'; - -function rejectWithCodeAndMessage(code, message) { - return Promise.reject( - getWebError({ - code, - message, - }), - ); -} - -// Converts a DataSnapshot to an object. -function snapshotToObject(snapshot) { - const childKeys = []; - - if (snapshot.hasChildren()) { - snapshot.forEach(childSnapshot => { - childKeys.push(childSnapshot.key); - }); - } - - return { - key: snapshot.key, - exists: snapshot.exists(), - hasChildren: snapshot.hasChildren(), - childrenCount: snapshot.size, - childKeys, - priority: snapshot.priority, - value: snapshot.val(), - }; -} - -function getDatabaseWebError(error) { - // Possible to override messages/codes here if necessary. - return getWebError(error); -} - -// Converts a DataSnapshot and previous child name to an object. -function snapshotWithPreviousChildToObject(snapshot, previousChildName) { - return { - snapshot: snapshotToObject(snapshot), - previousChildName, - }; -} - -const appInstances = {}; -const databaseInstances = {}; -const onDisconnectRef = {}; -const listeners = {}; -const emulatorForApp = {}; - -function getCachedAppInstance(appName) { - return (appInstances[appName] ??= getApp(appName)); -} - -// Returns a cached Database instance. -function getCachedDatabaseInstance(appName, dbURL) { - let instance = databaseInstances[`${appName}|${dbURL}`]; - if (!instance) { - instance = getDatabase(getCachedAppInstance(appName), dbURL); - // Relying on internals here so need to be careful between SDK versions. - if (emulatorForApp[appName] && !instance._instanceStarted) { - const { host, port } = emulatorForApp[appName]; - connectDatabaseEmulator(instance, host, port); - emulatorForApp[appName].connected = true; - } - } - return instance; -} - -// Returns a cached onDisconnect instance. -function getCachedOnDisconnectInstance(ref) { - return (onDisconnectRef[ref.key] ??= onDisconnect(ref)); -} - -export default { - /** - * Reconnects to the server. - * @param {string} appName - The app name. - * @param {string} dbURL - The database URL. - * @returns {Promise} - */ - goOnline(appName, dbURL) { - return guard(async () => { - const db = getCachedDatabaseInstance(appName, dbURL); - goOnline(db); - }); - }, - - /** - * Disconnects from the server. - * @param {string} appName - The app name. - * @param {string} dbURL - The database URL. - * @returns {Promise} - */ - goOffline(appName, dbURL) { - return guard(async () => { - const db = getCachedDatabaseInstance(appName, dbURL); - goOffline(db); - }); - }, - - setPersistenceEnabled() { - if (__DEV__) { - // eslint-disable-next-line no-console - console.warn( - 'The Firebase Database `setPersistenceEnabled` method is not available in the this environment.', - ); - } - return Promise.resolve(); - }, - - /** - * Sets the logging enabled state. - * @param {string} appName - The app name, not used. - * @param {string} dbURL - The database URL, not used. - * @param {boolean} enabled - The logging enabled state. - */ - setLoggingEnabled(_app, _dbURL, enabled) { - return guard(async () => { - enableLogging(enabled); - }); - }, - - setPersistenceCacheSizeBytes() { - // no-op on other platforms - return Promise.resolve(); - }, - - /** - * Connects to the Firebase database emulator. - * @param {string} appName - The app name. - * @param {string} dbURL - The database URL. - * @param {string} host - The emulator host. - * @param {number} port - The emulator - * @returns {Promise} - */ - useEmulator(appName, dbURL, host, port) { - return guard(async () => { - const db = getCachedDatabaseInstance(appName, dbURL); - connectDatabaseEmulator(db, host, port); - emulatorForApp[appName] = { host, port }; - }); - }, - - /** - * Reference - */ - - /** - * Sets a value at the specified path. - * @param {string} appName - The app name. - * @param {string} dbURL - The database URL. - * @param {string} path - The path. - * @param {object} props - The properties - * @returns {Promise} - */ - set(appName, dbURL, path, props) { - return guard(async () => { - const db = getCachedDatabaseInstance(appName, dbURL); - const dbRef = ref(db, path); - const value = props.value; - await set(dbRef, value); - }); - }, - - /** - * Updates the specified path with the provided values. - * @param {string} appName - The app name. - * @param {string} dbURL - The database URL. - * @param {string} path - The path. - * @param {object} props - The properties - * @returns {Promise} - */ - update(appName, dbURL, path, props) { - return guard(async () => { - const db = getCachedDatabaseInstance(appName, dbURL); - const dbRef = ref(db, path); - const values = props.values; - await update(dbRef, values); - }); - }, - - /** - * Sets a value at the specified path with a priority. - * @param {string} appName - The app name. - * @param {string} dbURL - The database URL. - * @param {string} path - The path. - * @param {object} props - The properties, including value and priority. - * @returns {Promise} - */ - setWithPriority(appName, dbURL, path, props) { - return guard(async () => { - const db = getCachedDatabaseInstance(appName, dbURL); - const dbRef = ref(db, path); - const value = props.value; - const priority = props.priority; - await setWithPriority(dbRef, value, priority); - }); - }, - - /** - * Removes the nodd at the specified path. - * @param {string} appName - The app name. - * @param {string} dbURL - The database URL. - * @param {string} path - The path. - * @returns {Promise} - */ - remove(appName, dbURL, path) { - return guard(async () => { - const db = getCachedDatabaseInstance(appName, dbURL); - const dbRef = ref(db, path); - await remove(dbRef); - }); - }, - - /** - * Sets the priority of the node at the specified path. - * @param {string} appName - The app name. - * @param {string} dbURL - The database URL. - * @param {string} path - The path. - * @param {object} props - The properties, including priority. - * @returns {Promise} - */ - setPriority(appName, dbURL, path, props) { - return guard(async () => { - const db = getCachedDatabaseInstance(appName, dbURL); - const dbRef = ref(db, path); - const priority = props.priority; - await setPriority(dbRef, priority); - }); - }, - - /** - * Query - */ - - /** - * Listens for data changes at the specified path once. - * @param {string} appName - The app name. - * @param {string} dbURL - The database URL. - * @param {string} path - The path. - * @param {object} modifiers - The modifiers. - * @param {string} eventType - The event type. - * @returns {Promise} - */ - once(appName, dbURL, path, modifiers, eventType) { - return guard(async () => { - const db = getCachedDatabaseInstance(appName, dbURL); - const dbRef = ref(db, path); - const queryRef = getQueryInstance(dbRef, modifiers); - - if (eventType === 'value') { - const snapshot = await new Promise((resolve, reject) => { - onValue(queryRef, resolve, reject, { onlyOnce: true }); - }); - - return snapshotToObject(snapshot); - } else { - let fn = null; - - if (eventType === 'child_added') { - fn = onChildAdded; - } else if (eventType === 'child_changed') { - fn = onChildChanged; - } else if (eventType === 'child_removed') { - fn = onChildRemoved; - } else if (eventType === 'child_moved') { - fn = onChildMoved; - } - - if (fn) { - const { snapshot, previousChildName } = await new Promise((resolve, reject) => { - fn( - queryRef, - (snapshot, previousChildName) => { - resolve({ snapshot, previousChildName }); - }, - reject, - { onlyOnce: true }, - ); - }); - - return snapshotWithPreviousChildToObject(snapshot, previousChildName); - } - } - - const snapshot = await get(dbRef, modifiers); - return snapshot; - }); - }, - - on(appName, dbURL, props) { - return guard(async () => { - const db = getCachedDatabaseInstance(appName, dbURL); - const { key, modifiers, path, eventType, registration } = props; - const { eventRegistrationKey } = registration; - const dbRef = ref(db, path); - - const queryRef = getQueryInstance(dbRef, modifiers); - - function sendEvent(data) { - const event = { - eventName: 'database_sync_event', - body: { - data, - key, - registration, - eventType, - }, - }; - - emitEvent('database_sync_event', event); - } - - function sendError(error) { - const event = { - eventName: 'database_sync_event', - body: { - key, - registration, - error: getDatabaseWebError(error), - }, - }; - - emitEvent('database_sync_event', event); - } - - let listener = null; - - // Ignore if the listener already exists. - if (listeners[eventRegistrationKey]) { - return; - } - - if (eventType === 'value') { - listener = onValue(queryRef, snapshot => sendEvent(snapshotToObject(snapshot)), sendError); - } else { - let fn = null; - - if (eventType === 'child_added') { - fn = onChildAdded; - } else if (eventType === 'child_changed') { - fn = onChildChanged; - } else if (eventType === 'child_removed') { - fn = onChildRemoved; - } else if (eventType === 'child_moved') { - fn = onChildMoved; - } - - if (fn) { - listener = fn( - queryRef, - (snapshot, previousChildName) => { - sendEvent(snapshotWithPreviousChildToObject(snapshot, previousChildName)); - }, - sendError, - ); - } - } - - listeners[eventRegistrationKey] = listener; - }); - }, - - off(_queryKey, eventRegistrationKey) { - const listener = listeners[eventRegistrationKey]; - if (listener) { - listener(); - delete listeners[eventRegistrationKey]; - } - }, - - keepSynced() { - return rejectWithCodeAndMessage( - 'unsupported', - 'This operation is not supported on this environment.', - ); - }, - - /** - * OnDisconnect - */ - - /** - * Cancels the onDisconnect instance at the specified path. - * @param {string} appName - The app name. - * @param {string} dbURL - The database URL. - * @param {string} path - The path. - * @returns {Promise} - */ - onDisconnectCancel(appName, dbURL, path) { - return guard(async () => { - const db = getCachedDatabaseInstance(appName, dbURL); - const dbRef = ref(db, path); - const instance = getCachedOnDisconnectInstance(dbRef); - await instance.cancel(); - - // Delete the onDisconnect instance from the cache. - delete onDisconnectRef[dbRef.key]; - }); - }, - - /** - * Sets a value to be written to the database on disconnect. - * @param {string} appName - The app name. - * @param {string} dbURL - The database URL. - * @param {string} path - The path. - * @returns {Promise} - */ - onDisconnectRemove(appName, dbURL, path) { - return guard(async () => { - const db = getCachedDatabaseInstance(appName, dbURL); - const dbRef = ref(db, path); - const instance = getCachedOnDisconnectInstance(dbRef); - await instance.remove(); - }); - }, - - /** - * Sets a value to be written to the database on disconnect. - * @param {string} appName - The app name. - * @param {string} dbURL - The database URL. - * @param {string} path - The path. - * @param {object} props - The properties, including value. - * @returns {Promise} - */ - onDisconnectSet(appName, dbURL, path, props) { - return guard(async () => { - const db = getCachedDatabaseInstance(appName, dbURL); - const dbRef = ref(db, path); - const instance = getCachedOnDisconnectInstance(dbRef); - const value = props.value; - await instance.set(value); - }); - }, - - /** - * Sets a value to be written to the database on disconnect with a priority. - * @param {string} appName - The app name. - * @param {string} dbURL - The database URL. - * @param {string} path - The path. - * @param {object} props - The properties, including value and priority. - * @returns {Promise} - */ - onDisconnectSetWithPriority(appName, dbURL, path, props) { - return guard(async () => { - const db = getCachedDatabaseInstance(appName, dbURL); - const dbRef = ref(db, path); - const instance = getCachedOnDisconnectInstance(dbRef); - const value = props.value; - const priority = props.priority; - await instance.setWithPriority(value, priority); - }); - }, - - /** - * Updates the specified path with the provided values on disconnect. - * @param {string} appName - The app name. - * @param {string} dbURL - The database URL. - * @param {string} path - The path. - * @param {object} props - The properties, including values. - * @returns {Promise} - */ - onDisconnectUpdate(appName, dbURL, path, props) { - return guard(async () => { - const db = getCachedDatabaseInstance(appName, dbURL); - const dbRef = ref(db, path); - const instance = getCachedOnDisconnectInstance(dbRef); - const values = props.values; - await instance.update(values); - }); - }, - - /** - * Transaction - */ - - transactionStart(appName, dbURL, path, transactionId, applyLocally, userExecutor) { - return guard(async () => { - const db = getCachedDatabaseInstance(appName, dbURL); - const dbRef = ref(db, path); - - try { - const { committed, snapshot } = await runTransaction(dbRef, userExecutor, { - applyLocally, - }); - - const event = { - body: { - committed, - type: 'complete', - snapshot: snapshotToObject(snapshot), - }, - appName, - id: transactionId, - eventName: 'database_transaction_event', - }; - - emitEvent('database_transaction_event', event); - } catch (e) { - const event = { - body: { - committed: false, - type: 'error', - error: getDatabaseWebError(e), - }, - appName, - id: transactionId, - eventName: 'database_transaction_event', - }; - - emitEvent('database_transaction_event', event); - } - }); - }, - - /** - * Commits the transaction with the specified updates. - * @param {string} appName - The app name. - * @param {string} dbURL - The database URL. - * @param {string} transactionId - The transaction ID. - * @param {object} updates - The updates. - * @returns {Promise} - */ - // eslint-disable-next-line @typescript-eslint/no-unused-vars - async transactionTryCommit(appName, dbURL, transactionId, updates) { - // We don't need to implement this as for 'Other' platforms - // we pass the users transaction function to the Firebase JS SDK directly. - throw new Error('Not implemented'); - }, -}; diff --git a/packages/database/lib/web/RNFBDatabaseModule.ts b/packages/database/lib/web/RNFBDatabaseModule.ts new file mode 100644 index 0000000000..69dce45158 --- /dev/null +++ b/packages/database/lib/web/RNFBDatabaseModule.ts @@ -0,0 +1,827 @@ +/* + * Copyright (c) 2016-present Invertase Limited & Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this library 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 { + getApp, + getDatabase, + connectDatabaseEmulator, + enableLogging, + goOnline, + goOffline, + ref, + set, + update, + setWithPriority, + remove, + setPriority, + onDisconnect, + onValue, + onChildAdded, + onChildChanged, + onChildMoved, + onChildRemoved, + runTransaction, + get, +} from '@react-native-firebase/app/dist/module/internal/web/firebaseDatabase'; +import { + guard, + getWebError, + emitEvent, +} from '@react-native-firebase/app/dist/module/internal/web/utils'; +import { getQueryInstance } from './query'; + +function rejectWithCodeAndMessage(code: string, message: string): Promise { + return Promise.reject( + getWebError({ + code, + message, + } as Error & { code?: string }), + ); +} + +interface SnapshotData { + key: string | null; + exists: boolean; + hasChildren: boolean; + childrenCount: number; + childKeys: string[]; + priority: string | number | null; + value: unknown; +} + +// Converts a DataSnapshot to an object. +function snapshotToObject(snapshot: { + key: string | null; + exists(): boolean; + hasChildren(): boolean; + size: number; + forEach: (callback: (child: { key: string | null }) => void) => void; + priority: string | number | null; + val(): unknown; +}): SnapshotData { + const childKeys: string[] = []; + + if (snapshot.hasChildren()) { + snapshot.forEach((childSnapshot: { key: string | null }) => { + childKeys.push(childSnapshot.key || ''); + }); + } + + return { + key: snapshot.key, + exists: snapshot.exists(), + hasChildren: snapshot.hasChildren(), + childrenCount: snapshot.size, + childKeys, + priority: snapshot.priority, + value: snapshot.val(), + }; +} + +function getDatabaseWebError(error: unknown): Error { + // Possible to override messages/codes here if necessary. + // @ts-expect-error - Web SDK error type doesn't match our Error type exactly + return getWebError(error); +} + +// Converts a DataSnapshot and previous child name to an object. +function snapshotWithPreviousChildToObject( + snapshot: { + key: string | null; + exists(): boolean; + hasChildren(): boolean; + size: number; + forEach: (callback: (child: { key: string | null }) => void) => void; + priority: string | number | null; + val(): unknown; + }, + previousChildName: string | null | undefined, +): { snapshot: SnapshotData; previousChildName: string | null | undefined } { + return { + snapshot: snapshotToObject(snapshot), + previousChildName, + }; +} + +const appInstances: Record = {}; +const databaseInstances: Record = {}; +const onDisconnectRef: Record = {}; +const listeners: Record void> = {}; +const emulatorForApp: Record = {}; + +function getCachedAppInstance(appName: string): unknown { + return (appInstances[appName] ??= getApp(appName) as unknown); +} + +// Returns a cached Database instance. +function getCachedDatabaseInstance(appName: string, dbURL: string): unknown { + let instance = databaseInstances[`${appName}|${dbURL}`]; + if (!instance) { + // @ts-expect-error - Web SDK types don't match our Database type exactly + instance = getDatabase(getCachedAppInstance(appName), dbURL); + // Relying on internals here so need to be careful between SDK versions. + if (emulatorForApp[appName] && !(instance as { _instanceStarted?: boolean })._instanceStarted) { + const { host, port } = emulatorForApp[appName]!; + // @ts-expect-error - Web SDK types don't match our Database type exactly + connectDatabaseEmulator(instance, host, port); + emulatorForApp[appName]!.connected = true; + } + } + return instance; +} + +// Returns a cached onDisconnect instance. +function getCachedOnDisconnectInstance(ref: { key: string | null }): unknown { + // @ts-expect-error - Web SDK types don't match our Reference type exactly + return (onDisconnectRef[ref.key || ''] ??= onDisconnect(ref)); +} + +export default { + /** + * Reconnects to the server. + * @param appName - The app name. + * @param dbURL - The database URL. + * @returns {Promise} + */ + goOnline(appName: string, dbURL: string): Promise { + return guard(async () => { + const db = getCachedDatabaseInstance(appName, dbURL); + // @ts-expect-error - Web SDK types don't match our Database type exactly + goOnline(db); + }); + }, + + /** + * Disconnects from the server. + * @param appName - The app name. + * @param dbURL - The database URL. + * @returns {Promise} + */ + goOffline(appName: string, dbURL: string): Promise { + return guard(async () => { + const db = getCachedDatabaseInstance(appName, dbURL); + // @ts-expect-error - Web SDK types don't match our Database type exactly + goOffline(db); + }); + }, + + setPersistenceEnabled(): Promise { + if (__DEV__) { + // eslint-disable-next-line no-console + console.warn( + 'The Firebase Database `setPersistenceEnabled` method is not available in the this environment.', + ); + } + return Promise.resolve(); + }, + + /** + * Sets the logging enabled state. + * @param _app - The app name, not used. + * @param _dbURL - The database URL, not used. + * @param enabled - The logging enabled state. + */ + setLoggingEnabled(_app: string, _dbURL: string, enabled: boolean): Promise { + return guard(async () => { + enableLogging(enabled); + }); + }, + + setPersistenceCacheSizeBytes(): Promise { + // no-op on other platforms + return Promise.resolve(); + }, + + /** + * Connects to the Firebase database emulator. + * @param appName - The app name. + * @param dbURL - The database URL. + * @param host - The emulator host. + * @param port - The emulator port + * @returns {Promise} + */ + useEmulator(appName: string, dbURL: string, host: string, port: number): Promise { + return guard(async () => { + const db = getCachedDatabaseInstance(appName, dbURL); + // @ts-expect-error - Web SDK types don't match our Database type exactly + connectDatabaseEmulator(db, host, port); + emulatorForApp[appName] = { host, port }; + }); + }, + + /** + * Reference + */ + + /** + * Sets a value at the specified path. + * @param appName - The app name. + * @param dbURL - The database URL. + * @param path - The path. + * @param props - The properties + * @returns {Promise} + */ + set(appName: string, dbURL: string, path: string, props: { value: unknown }): Promise { + return guard(async () => { + const db = getCachedDatabaseInstance(appName, dbURL); + // @ts-expect-error - Web SDK types don't match our Database type exactly + const dbRef = ref(db, path); + const value = props.value; + await set(dbRef, value); + }); + }, + + /** + * Updates the specified path with the provided values. + * @param appName - The app name. + * @param dbURL - The database URL. + * @param path - The path. + * @param props - The properties + * @returns {Promise} + */ + update( + appName: string, + dbURL: string, + path: string, + props: { values: { [key: string]: unknown } }, + ): Promise { + return guard(async () => { + const db = getCachedDatabaseInstance(appName, dbURL); + // @ts-expect-error - Web SDK types don't match our Database type exactly + const dbRef = ref(db, path); + const values = props.values; + await update(dbRef, values); + }); + }, + + /** + * Sets a value at the specified path with a priority. + * @param appName - The app name. + * @param dbURL - The database URL. + * @param path - The path. + * @param props - The properties, including value and priority. + * @returns {Promise} + */ + setWithPriority( + appName: string, + dbURL: string, + path: string, + props: { value: unknown; priority: string | number | null }, + ): Promise { + return guard(async () => { + const db = getCachedDatabaseInstance(appName, dbURL); + // @ts-expect-error - Web SDK types don't match our Database type exactly + const dbRef = ref(db, path); + const value = props.value; + const priority = props.priority; + await setWithPriority(dbRef, value, priority); + }); + }, + + /** + * Removes the node at the specified path. + * @param appName - The app name. + * @param dbURL - The database URL. + * @param path - The path. + * @returns {Promise} + */ + remove(appName: string, dbURL: string, path: string): Promise { + return guard(async () => { + const db = getCachedDatabaseInstance(appName, dbURL); + // @ts-expect-error - Web SDK types don't match our Database type exactly + const dbRef = ref(db, path); + await remove(dbRef); + }); + }, + + /** + * Sets the priority of the node at the specified path. + * @param appName - The app name. + * @param dbURL - The database URL. + * @param path - The path. + * @param props - The properties, including priority. + * @returns {Promise} + */ + setPriority( + appName: string, + dbURL: string, + path: string, + props: { priority: string | number | null }, + ): Promise { + return guard(async () => { + const db = getCachedDatabaseInstance(appName, dbURL); + // @ts-expect-error - Web SDK types don't match our Database type exactly + const dbRef = ref(db, path); + const priority = props.priority; + await setPriority(dbRef, priority); + }); + }, + + /** + * Query + */ + + /** + * Listens for data changes at the specified path once. + * @param appName - The app name. + * @param dbURL - The database URL. + * @param path - The path. + * @param modifiers - The modifiers. + * @param eventType - The event type. + * @returns {Promise} + */ + once( + appName: string, + dbURL: string, + path: string, + modifiers: unknown[], + eventType: string, + ): Promise< + SnapshotData | { snapshot: SnapshotData; previousChildName: string | null | undefined } + > { + return guard(async () => { + const db = getCachedDatabaseInstance(appName, dbURL); + // @ts-expect-error - Web SDK types don't match our Database type exactly + const dbRef = ref(db, path); + const queryRef = getQueryInstance( + dbRef, + modifiers as Array<{ type: string; name: string; key?: string; value?: unknown }>, + ); + + if (eventType === 'value') { + const snapshot = await new Promise<{ + key: string | null; + exists(): boolean; + hasChildren(): boolean; + size: number; + forEach: (callback: (child: { key: string | null }) => void) => void; + priority: string | number | null; + val(): unknown; + }>((resolve, reject) => { + // @ts-expect-error - Web SDK types don't match our Query type exactly + onValue(queryRef, resolve as (snapshot: unknown) => void, reject, { onlyOnce: true }); + }); + + return snapshotToObject(snapshot); + } else { + let fn: + | (( + queryRef: unknown, + callback: (snapshot: unknown, previousChildName: string | null) => void, + reject: (error: Error) => void, + options: { onlyOnce: boolean }, + ) => void) + | null = null; + + if (eventType === 'child_added') { + // @ts-expect-error - Web SDK types don't match our Query type exactly + fn = onChildAdded; + } else if (eventType === 'child_changed') { + // @ts-expect-error - Web SDK types don't match our Query type exactly + fn = onChildChanged; + } else if (eventType === 'child_removed') { + // @ts-expect-error - Web SDK types don't match our Query type exactly + fn = onChildRemoved; + } else if (eventType === 'child_moved') { + // @ts-expect-error - Web SDK types don't match our Query type exactly + fn = onChildMoved; + } + + if (fn) { + const { snapshot, previousChildName } = await new Promise<{ + snapshot: { + key: string | null; + exists(): boolean; + hasChildren(): boolean; + size: number; + forEach: (callback: (child: { key: string | null }) => void) => void; + priority: string | number | null; + val(): unknown; + }; + previousChildName: string | null | undefined; + }>((resolve, reject) => { + ( + fn as unknown as ( + queryRef: unknown, + callback: (snapshot: unknown, previousChildName?: string | null) => void, + reject: (error: Error) => void, + options: { onlyOnce: boolean }, + ) => void + )( + queryRef, + (snapshot, previousChildName) => { + resolve({ + snapshot: snapshot as { + key: string | null; + exists(): boolean; + hasChildren(): boolean; + size: number; + forEach: (callback: (child: { key: string | null }) => void) => void; + priority: string | number | null; + val(): unknown; + }, + previousChildName, + }); + }, + reject, + { onlyOnce: true }, + ); + }); + + return snapshotWithPreviousChildToObject(snapshot, previousChildName ?? null); + } + } + + // @ts-expect-error - Web SDK types don't match our Reference type exactly + const snapshot = await get(dbRef, modifiers); + return snapshotToObject( + snapshot as { + key: string | null; + exists(): boolean; + hasChildren(): boolean; + size: number; + forEach: (callback: (child: { key: string | null }) => void) => void; + priority: string | number | null; + val(): unknown; + }, + ); + }); + }, + + on( + appName: string, + dbURL: string, + props: { + key: string; + modifiers: unknown[]; + path: string; + eventType: string; + registration: { + eventRegistrationKey: string; + }; + }, + ): Promise { + return guard(async () => { + const db = getCachedDatabaseInstance(appName, dbURL); + const { key, modifiers, path, eventType, registration } = props; + const { eventRegistrationKey } = registration; + // @ts-expect-error - Web SDK types don't match our Database type exactly + const dbRef = ref(db, path); + + const queryRef = getQueryInstance( + dbRef, + modifiers as Array<{ type: string; name: string; key?: string; value?: unknown }>, + ); + + function sendEvent( + data: + | SnapshotData + | { snapshot: SnapshotData; previousChildName: string | null | undefined }, + ): void { + const event = { + eventName: 'database_sync_event', + body: { + data, + key, + registration, + eventType, + }, + }; + + emitEvent('database_sync_event', event); + } + + function sendError(error: Error): void { + const event = { + eventName: 'database_sync_event', + body: { + key, + registration, + error: getDatabaseWebError(error), + }, + }; + + emitEvent('database_sync_event', event); + } + + let listener: (() => void) | null = null; + + // Ignore if the listener already exists. + if (listeners[eventRegistrationKey]) { + return; + } + + if (eventType === 'value') { + listener = (onValue as any)( + queryRef, + (snapshot: { + key: string | null; + exists(): boolean; + hasChildren(): boolean; + size: number; + forEach: (callback: (child: { key: string | null }) => void) => void; + priority: string | number | null; + val(): unknown; + }) => sendEvent(snapshotToObject(snapshot)), + sendError, + ) as () => void; + } else { + let fn: + | (( + queryRef: unknown, + callback: (snapshot: unknown, previousChildName: string | null) => void, + reject: (error: Error) => void, + ) => () => void) + | null = null; + + if (eventType === 'child_added') { + // @ts-expect-error - Web SDK types don't match our Query type exactly + fn = onChildAdded; + } else if (eventType === 'child_changed') { + // @ts-expect-error - Web SDK types don't match our Query type exactly + fn = onChildChanged; + } else if (eventType === 'child_removed') { + // @ts-expect-error - Web SDK types don't match our Query type exactly + fn = onChildRemoved; + } else if (eventType === 'child_moved') { + // @ts-expect-error - Web SDK types don't match our Query type exactly + fn = onChildMoved; + } + + if (fn) { + listener = ( + fn as unknown as ( + queryRef: unknown, + callback: (snapshot: unknown, previousChildName?: string | null) => void, + reject: (error: Error) => void, + ) => () => void + )( + queryRef, + (snapshot, previousChildName) => { + sendEvent( + snapshotWithPreviousChildToObject( + snapshot as { + key: string | null; + exists(): boolean; + hasChildren(): boolean; + size: number; + forEach: (callback: (child: { key: string | null }) => void) => void; + priority: string | number | null; + val(): unknown; + }, + previousChildName ?? null, + ), + ); + }, + sendError, + ) as () => void; + } + } + + listeners[eventRegistrationKey] = listener!; + }); + }, + + off(_queryKey: string, eventRegistrationKey: string): void { + const listener = listeners[eventRegistrationKey]; + if (listener) { + listener(); + delete listeners[eventRegistrationKey]; + } + }, + + keepSynced(): Promise { + return rejectWithCodeAndMessage( + 'unsupported', + 'This operation is not supported on this environment.', + ); + }, + + /** + * OnDisconnect + */ + + /** + * Cancels the onDisconnect instance at the specified path. + * @param appName - The app name. + * @param dbURL - The database URL. + * @param path - The path. + * @returns {Promise} + */ + onDisconnectCancel(appName: string, dbURL: string, path: string): Promise { + return guard(async () => { + const db = getCachedDatabaseInstance(appName, dbURL); + // @ts-expect-error - Web SDK types don't match our Database type exactly + const dbRef = ref(db, path); + const instance = getCachedOnDisconnectInstance( + dbRef as unknown as { key: string | null }, + ) as { + cancel: () => Promise; + }; + await instance.cancel(); + + // Delete the onDisconnect instance from the cache. + delete onDisconnectRef[(dbRef as unknown as { key: string | null }).key || '']; + }); + }, + + /** + * Sets a value to be written to the database on disconnect. + * @param appName - The app name. + * @param dbURL - The database URL. + * @param path - The path. + * @returns {Promise} + */ + onDisconnectRemove(appName: string, dbURL: string, path: string): Promise { + return guard(async () => { + const db = getCachedDatabaseInstance(appName, dbURL); + // @ts-expect-error - Web SDK types don't match our Database type exactly + const dbRef = ref(db, path); + const instance = getCachedOnDisconnectInstance( + dbRef as unknown as { key: string | null }, + ) as { + remove: () => Promise; + }; + await instance.remove(); + }); + }, + + /** + * Sets a value to be written to the database on disconnect. + * @param appName - The app name. + * @param dbURL - The database URL. + * @param path - The path. + * @param props - The properties, including value. + * @returns {Promise} + */ + onDisconnectSet( + appName: string, + dbURL: string, + path: string, + props: { value: unknown }, + ): Promise { + return guard(async () => { + const db = getCachedDatabaseInstance(appName, dbURL); + // @ts-expect-error - Web SDK types don't match our Database type exactly + const dbRef = ref(db, path); + const instance = getCachedOnDisconnectInstance( + dbRef as unknown as { key: string | null }, + ) as { + set: (value: unknown) => Promise; + }; + const value = props.value; + await instance.set(value); + }); + }, + + /** + * Sets a value to be written to the database on disconnect with a priority. + * @param appName - The app name. + * @param dbURL - The database URL. + * @param path - The path. + * @param props - The properties, including value and priority. + * @returns {Promise} + */ + onDisconnectSetWithPriority( + appName: string, + dbURL: string, + path: string, + props: { value: unknown; priority: string | number | null }, + ): Promise { + return guard(async () => { + const db = getCachedDatabaseInstance(appName, dbURL); + // @ts-expect-error - Web SDK types don't match our Database type exactly + const dbRef = ref(db, path); + const instance = getCachedOnDisconnectInstance( + dbRef as unknown as { key: string | null }, + ) as { + setWithPriority: (value: unknown, priority: string | number | null) => Promise; + }; + const value = props.value; + const priority = props.priority; + await instance.setWithPriority(value, priority); + }); + }, + + /** + * Updates the specified path with the provided values on disconnect. + * @param appName - The app name. + * @param dbURL - The database URL. + * @param path - The path. + * @param props - The properties, including values. + * @returns {Promise} + */ + onDisconnectUpdate( + appName: string, + dbURL: string, + path: string, + props: { values: { [key: string]: unknown } }, + ): Promise { + return guard(async () => { + const db = getCachedDatabaseInstance(appName, dbURL); + // @ts-expect-error - Web SDK types don't match our Database type exactly + const dbRef = ref(db, path); + const instance = getCachedOnDisconnectInstance( + dbRef as unknown as { key: string | null }, + ) as { + update: (values: { [key: string]: unknown }) => Promise; + }; + const values = props.values; + await instance.update(values); + }); + }, + + /** + * Transaction + */ + + transactionStart( + appName: string, + dbURL: string, + path: string, + transactionId: number, + applyLocally: boolean, + userExecutor: (currentData: unknown) => unknown, + ): Promise { + return guard(async () => { + const db = getCachedDatabaseInstance(appName, dbURL); + // @ts-expect-error - Web SDK types don't match our Database type exactly + const dbRef = ref(db, path); + + try { + const { committed, snapshot } = (await (runTransaction as any)(dbRef, userExecutor, { + applyLocally, + })) as { committed: boolean; snapshot: unknown }; + + const event = { + body: { + committed, + type: 'complete', + snapshot: snapshotToObject( + snapshot as { + key: string | null; + exists(): boolean; + hasChildren(): boolean; + size: number; + forEach: (callback: (child: { key: string | null }) => void) => void; + priority: string | number | null; + val(): unknown; + }, + ), + }, + appName, + id: transactionId, + eventName: 'database_transaction_event', + }; + + emitEvent('database_transaction_event', event); + } catch (e) { + const event = { + body: { + committed: false, + type: 'error', + error: getDatabaseWebError(e), + }, + appName, + id: transactionId, + eventName: 'database_transaction_event', + }; + + emitEvent('database_transaction_event', event); + } + }); + }, + + /** + * Commits the transaction with the specified updates. + * @param appName - The app name. + * @param dbURL - The database URL. + * @param transactionId - The transaction ID. + * @param updates - The updates. + * @returns {Promise} + */ + async transactionTryCommit( + _appName: string, + _dbURL: string, + _transactionId: number, + _updates: unknown, + ): Promise { + // We don't need to implement this as for 'Other' platforms + // we pass the users transaction function to the Firebase JS SDK directly. + throw new Error('Not implemented'); + }, +}; diff --git a/packages/database/lib/web/query.js b/packages/database/lib/web/query.js deleted file mode 100644 index 92903c9c17..0000000000 --- a/packages/database/lib/web/query.js +++ /dev/null @@ -1,71 +0,0 @@ -import { - query, - orderByKey, - orderByPriority, - orderByValue, - orderByChild, - limitToLast, - limitToFirst, - endAt, - endBefore, - startAt, - startAfter, -} from '@react-native-firebase/app/dist/module/internal/web/firebaseDatabase'; - -export function getQueryInstance(dbRef, modifiers) { - const constraints = []; - - for (const modifier of modifiers) { - const { type, name } = modifier; - - if (type === 'orderBy') { - switch (name) { - case 'orderByKey': - constraints.push(orderByKey()); - break; - case 'orderByPriority': - constraints.push(orderByPriority()); - break; - case 'orderByValue': - constraints.push(orderByValue()); - break; - case 'orderByChild': - constraints.push(orderByChild(modifier.key)); - break; - } - } - - if (type === 'limit') { - const { value } = modifier; - - switch (name) { - case 'limitToLast': - constraints.push(limitToLast(value)); - break; - case 'limitToFirst': - constraints.push(limitToFirst(value)); - break; - } - } - - if (type === 'filter') { - const { key, value } = modifier; - - switch (name) { - case 'endAt': - constraints.push(endAt(value, key)); - break; - case 'endBefore': - constraints.push(endBefore(value, key)); - break; - case 'startAt': - constraints.push(startAt(value, key)); - break; - case 'startAfter': - constraints.push(startAfter(value, key)); - break; - } - } - } - return query(dbRef, ...constraints); -} diff --git a/packages/database/lib/web/query.ts b/packages/database/lib/web/query.ts new file mode 100644 index 0000000000..5e7475a2f4 --- /dev/null +++ b/packages/database/lib/web/query.ts @@ -0,0 +1,96 @@ +/* + * Copyright (c) 2016-present Invertase Limited & Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this library 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 { + query, + orderByKey, + orderByPriority, + orderByValue, + orderByChild, + limitToLast, + limitToFirst, + endAt, + endBefore, + startAt, + startAfter, + type QueryConstraint, +} from '@react-native-firebase/app/dist/module/internal/web/firebaseDatabase'; + +interface QueryModifier { + type: string; + name: string; + key?: string; + value?: unknown; +} + +export function getQueryInstance(dbRef: unknown, modifiers: QueryModifier[]): unknown { + const constraints: QueryConstraint[] = []; + + for (const modifier of modifiers) { + const { type, name } = modifier; + + if (type === 'orderBy') { + switch (name) { + case 'orderByKey': + constraints.push(orderByKey()); + break; + case 'orderByPriority': + constraints.push(orderByPriority()); + break; + case 'orderByValue': + constraints.push(orderByValue()); + break; + case 'orderByChild': + constraints.push(orderByChild(modifier.key!)); + break; + } + } + + if (type === 'limit') { + const { value } = modifier; + + switch (name) { + case 'limitToLast': + constraints.push(limitToLast(value as number)); + break; + case 'limitToFirst': + constraints.push(limitToFirst(value as number)); + break; + } + } + + if (type === 'filter') { + const { key, value } = modifier; + + switch (name) { + case 'endAt': + constraints.push(endAt(value as number | string | boolean | null, key)); + break; + case 'endBefore': + constraints.push(endBefore(value as number | string | boolean | null, key)); + break; + case 'startAt': + constraints.push(startAt(value as number | string | boolean | null | undefined, key)); + break; + case 'startAfter': + constraints.push(startAfter(value as number | string | boolean | null, key)); + break; + } + } + } + return query(dbRef as Parameters[0], ...constraints); +} diff --git a/packages/database/package.json b/packages/database/package.json index 278e74b808..4ed1f367b6 100644 --- a/packages/database/package.json +++ b/packages/database/package.json @@ -3,12 +3,13 @@ "version": "23.8.6", "author": "Invertase (http://invertase.io)", "description": "React Native Firebase - The Firebase Realtime Database is a cloud-hosted database. Data is stored as JSON and synchronized in realtime to every connected client. React Native Firebase provides native integration with the Android & iOS Firebase SDKs, supporting both realtime data sync and offline capabilities.", - "main": "lib/index.js", - "types": "lib/index.d.ts", + "main": "./lib/index.ts", + "types": "./lib/index.ts", "scripts": { - "build": "genversion --semi lib/version.js", + "build": "genversion --esm --semi lib/version.ts", "build:clean": "rimraf android/build && rimraf ios/build", - "prepare": "yarn run build" + "compile": "bob build", + "prepare": "yarn run build && yarn compile" }, "repository": { "type": "git", @@ -30,5 +31,38 @@ "publishConfig": { "access": "public", "provenance": true - } + }, + "devDependencies": { + "react-native-builder-bob": "^0.40.17" + }, + "exports": { + ".": { + "source": "./lib/index.ts", + "types": "./dist/typescript/lib/index.d.ts", + "default": "./dist/module/index.js" + }, + "./package.json": "./package.json" + }, + "react-native-builder-bob": { + "source": "lib", + "output": "dist", + "targets": [ + [ + "module", + { + "esm": true + } + ], + [ + "typescript", + { + "tsc": "../../node_modules/.bin/tsc" + } + ] + ] + }, + "eslintIgnore": [ + "node_modules/", + "dist/" + ] } diff --git a/packages/database/tsconfig.json b/packages/database/tsconfig.json new file mode 100644 index 0000000000..48793d3809 --- /dev/null +++ b/packages/database/tsconfig.json @@ -0,0 +1,23 @@ +{ + "extends": "../../tsconfig.packages.base.json", + "compilerOptions": { + "baseUrl": ".", + "rootDir": ".", + "paths": { + "@react-native-firebase/app/dist/module/common/*": ["../app/dist/typescript/lib/common/*"], + "@react-native-firebase/app/dist/module/common": ["../app/dist/typescript/lib/common"], + "@react-native-firebase/app/dist/module/internal/web/*": [ + "../app/dist/typescript/lib/internal/web/*" + ], + "@react-native-firebase/app/dist/module/internal/*": [ + "../app/dist/typescript/lib/internal/*" + ], + "@react-native-firebase/app/dist/module/internal": ["../app/dist/typescript/lib/internal"], + "@react-native-firebase/app": ["../app/dist/typescript/lib"], + "@react-native-firebase/app/dist/module/types/internal": ["../app/dist/typescript/lib/types/internal"], + "@react-native-firebase/app/dist/module/types/common": ["../app/dist/typescript/lib/types/common"], + } + }, + "include": ["lib/**/*"], + "exclude": ["node_modules", "dist", "__tests__", "**/*.test.ts"] +} diff --git a/packages/database/type-test.ts b/packages/database/type-test.ts index d5e1064154..71a95faab4 100644 --- a/packages/database/type-test.ts +++ b/packages/database/type-test.ts @@ -159,9 +159,8 @@ setPersistenceCacheSizeBytes(dbInstance, 2000000); const timestamp = serverTimestamp(); console.log(timestamp); -getServerTime(dbInstance).then((time: number) => { - console.log(time); -}); +const modularServerTime = getServerTime(dbInstance); +console.log(modularServerTime); const incrementValue = increment(1); console.log(incrementValue); @@ -215,7 +214,7 @@ modularUnsubscribe3(); const unsubscribeChildAdded = onChildAdded( testRef, - (snapshot: FirebaseDatabaseTypes.DataSnapshot, previousChildName: string | null) => { + (snapshot: FirebaseDatabaseTypes.DataSnapshot, previousChildName?: string | null) => { console.log(snapshot.val()); console.log(previousChildName); }, @@ -223,7 +222,7 @@ const unsubscribeChildAdded = onChildAdded( const unsubscribeChildChanged = onChildChanged( testRef, - (snapshot: FirebaseDatabaseTypes.DataSnapshot, previousChildName: string | null) => { + (snapshot: FirebaseDatabaseTypes.DataSnapshot, previousChildName?: string | null) => { console.log(snapshot.val()); console.log(previousChildName); }, @@ -231,7 +230,7 @@ const unsubscribeChildChanged = onChildChanged( const unsubscribeChildMoved = onChildMoved( testRef, - (snapshot: FirebaseDatabaseTypes.DataSnapshot, previousChildName: string | null) => { + (snapshot: FirebaseDatabaseTypes.DataSnapshot, previousChildName?: string | null) => { console.log(snapshot.val()); console.log(previousChildName); }, diff --git a/tsconfig.json b/tsconfig.json index 0658ab77a9..7112f7aabe 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,6 +1,7 @@ { "include": [ "packages/ai/lib/types/polyfills.d.ts", + "packages/app/lib/index.d.ts", "packages/app-check/lib/index.d.ts", "packages/app-check/lib/modular/index.d.ts", "packages/app-distribution/lib/index.d.ts", @@ -11,10 +12,6 @@ "packages/auth/lib/modular/index.d.ts", "packages/crashlytics/lib/index.d.ts", "packages/crashlytics/lib/modular/index.d.ts", - "packages/database/lib/index.d.ts", - "packages/database/lib/modular/index.d.ts", - "packages/database/lib/modular/query.d.ts", - "packages/database/lib/modular/transaction.d.ts", "packages/firestore/lib/index.d.ts", "packages/firestore/lib/modular/Bytes.d.ts", "packages/firestore/lib/modular/FieldPath.d.ts", @@ -76,7 +73,15 @@ "lib": ["es2015", "es2016", "esnext", "dom"], "types": ["react-native", "node"], "paths": { - "@react-native-firebase/*": ["./packages/*/lib/index.d.ts"] + "@react-native-firebase/*": ["./packages/*/lib/index.d.ts", "./packages/*/lib/index.ts"], + "@react-native-firebase/app": ["./packages/app/lib/index.ts", "./packages/app/lib/index.d.ts"], + "@react-native-firebase/app/dist/module/common/*": ["./packages/app/lib/common/*"], + "@react-native-firebase/app/dist/module/common": ["./packages/app/lib/common"], + "@react-native-firebase/app/dist/module/internal/web/*": ["./packages/app/lib/internal/web/*"], + "@react-native-firebase/app/dist/module/internal/*": ["./packages/app/lib/internal/*"], + "@react-native-firebase/app/dist/module/internal": ["./packages/app/lib/internal"], + "@react-native-firebase/app/dist/module/types/internal": ["./packages/app/lib/types/internal"], + "@react-native-firebase/app/dist/module/types/common": ["./packages/app/lib/types/common"] } }, "exclude": ["node_modules", "**/*.spec.ts", "packages/**/dist"] diff --git a/yarn.lock b/yarn.lock index a7c0fb5e7a..26abdf55c8 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6210,6 +6210,8 @@ __metadata: "@react-native-firebase/database@npm:23.8.6, @react-native-firebase/database@workspace:packages/database": version: 0.0.0-use.local resolution: "@react-native-firebase/database@workspace:packages/database" + dependencies: + react-native-builder-bob: "npm:^0.40.17" peerDependencies: "@react-native-firebase/app": 23.8.6 languageName: unknown