From d98269499ff679ad8f13feaa66c40585b0543669 Mon Sep 17 00:00:00 2001 From: Maneesh Tewani Date: Mon, 27 Oct 2025 14:10:18 -0700 Subject: [PATCH 1/8] Fixed onComplete for FDC --- packages/data-connect/src/api.browser.ts | 1 + packages/data-connect/src/api/query.ts | 1 + .../data-connect/src/core/QueryManager.ts | 5 + packages/data-connect/test/queries.test.ts | 3 + .../data-connect/test/unit/queries.test.ts | 95 ++++++++++++++++++- 5 files changed, 104 insertions(+), 1 deletion(-) diff --git a/packages/data-connect/src/api.browser.ts b/packages/data-connect/src/api.browser.ts index 1ffcb8d1647..d31c3253537 100644 --- a/packages/data-connect/src/api.browser.ts +++ b/packages/data-connect/src/api.browser.ts @@ -102,6 +102,7 @@ export function subscribe( return ref.dataConnect._queryManager.addSubscription( ref, onResult, + onComplete, onError, initialCache ); diff --git a/packages/data-connect/src/api/query.ts b/packages/data-connect/src/api/query.ts index a1cd0726160..101a884bf6d 100644 --- a/packages/data-connect/src/api/query.ts +++ b/packages/data-connect/src/api/query.ts @@ -45,6 +45,7 @@ export type QueryUnsubscribe = () => void; export interface DataConnectSubscription { userCallback: OnResultSubscription; errCallback?: (e?: DataConnectError) => void; + onCompleteCallback?: () => void; unsubscribe: () => void; } diff --git a/packages/data-connect/src/core/QueryManager.ts b/packages/data-connect/src/core/QueryManager.ts index 8b7c59aea85..c1f3969dc22 100644 --- a/packages/data-connect/src/core/QueryManager.ts +++ b/packages/data-connect/src/core/QueryManager.ts @@ -17,6 +17,7 @@ import { DataConnectSubscription, + OnCompleteSubscription, OnErrorSubscription, OnResultSubscription, QueryPromise, @@ -97,6 +98,7 @@ export class QueryManager { addSubscription( queryRef: OperationRef, onResultCallback: OnResultSubscription, + onCompleteCallback: OnCompleteSubscription, onErrorCallback?: OnErrorSubscription, initialCache?: OpResult ): () => void { @@ -111,13 +113,16 @@ export class QueryManager { >; const subscription = { userCallback: onResultCallback, + onCompleteCallback, errCallback: onErrorCallback }; const unsubscribe = (): void => { const trackedQuery = this._queries.get(key)!; + // TODO: Trigger onComplete trackedQuery.subscriptions = trackedQuery.subscriptions.filter( sub => sub !== subscription ); + onCompleteCallback(); }; if (initialCache && trackedQuery.currentCache !== initialCache) { logDebug('Initial cache found. Comparing dates.'); diff --git a/packages/data-connect/test/queries.test.ts b/packages/data-connect/test/queries.test.ts index 24db1e4508f..fbee3707873 100644 --- a/packages/data-connect/test/queries.test.ts +++ b/packages/data-connect/test/queries.test.ts @@ -17,6 +17,7 @@ import { expect, use } from 'chai'; import chaiAsPromised from 'chai-as-promised'; +import sinonChai from 'sinon-chai'; import { connectDataConnectEmulator, @@ -38,6 +39,7 @@ import { import { getConnectionConfig, initDatabase, PROJECT_ID } from './util'; use(chaiAsPromised); +use(sinonChai); interface Post { id: string; @@ -128,6 +130,7 @@ describe('DataConnect Tests', async () => { }); expect(res.source).to.eq(SOURCE_SERVER); }); + it(`returns the result source as cache when data already exists`, async () => { const taskListQuery = getPostsRef(); const queryResult = await executeQuery(taskListQuery); diff --git a/packages/data-connect/test/unit/queries.test.ts b/packages/data-connect/test/unit/queries.test.ts index 68bd96268a6..2bb12953370 100644 --- a/packages/data-connect/test/unit/queries.test.ts +++ b/packages/data-connect/test/unit/queries.test.ts @@ -21,13 +21,14 @@ import * as chai from 'chai'; import chaiAsPromised from 'chai-as-promised'; import * as sinon from 'sinon'; -import { DataConnectOptions } from '../../src'; +import { DataConnectOptions, QueryRef, queryRef, subscribe } from '../../src'; import { AuthTokenListener, AuthTokenProvider } from '../../src/core/FirebaseAuthProvider'; import { initializeFetch } from '../../src/network/fetch'; import { RESTTransport } from '../../src/network/transport/rest'; +import { initDatabase } from '../util'; chai.use(chaiAsPromised); const options: DataConnectOptions = { connector: 'c', @@ -61,10 +62,102 @@ const fakeFetchImpl = sinon.stub().returns( status: 401 } as Response) ); +interface PostVariables { + testId: string; +} +const TEST_ID = crypto.randomUUID(); +interface PostListResponse { + posts: Post[]; +} +interface Post { + id: string; + description: string; +} +function getPostsRef(): QueryRef { + const dc = initDatabase(); + return queryRef(dc, 'ListPosts', { + testId: TEST_ID + }); +} describe('Queries', () => { afterEach(() => { fakeFetchImpl.resetHistory(); }); + it('should call onComplete callback after subscribe is called', async () => { + const taskListQuery = getPostsRef(); + const onCompleteUserStub = sinon.stub(); + const unsubscribe = subscribe(taskListQuery, { + onNext: () => {}, + onComplete: onCompleteUserStub + }); + expect(onCompleteUserStub).to.not.have.been.called; + unsubscribe(); + expect(onCompleteUserStub).to.have.been.calledOnce; + }); + it('should call onErr callback after a 401 occurs', async () => { + const json = {}; + const throwErrorFakeImpl = sinon.stub().returns( + Promise.resolve({ + json: () => { + return Promise.resolve(json); + }, + status: 401 + } as Response) + ); + initializeFetch(throwErrorFakeImpl); + const taskListQuery = getPostsRef(); + const onErrStub = sinon.stub(); + let unsubscribeFn: (() => void) | null = null; + const promise = new Promise((resolve, reject) => { + unsubscribeFn = subscribe(taskListQuery, { + onNext: () => { + resolve(null); + }, + onComplete: () => {}, + onErr: err => { + onErrStub(); + reject(err); + } + }); + }); + expect(onErrStub).not.to.have.been.called; + await expect(promise).to.have.eventually.been.rejected; + expect(onErrStub).to.have.been.calledOnce; + unsubscribeFn!(); + }); + it('should call onErr callback after a graphql error occurs', async () => { + const json = { + errors: [{ something: 'abc' }] + }; + const throwErrorFakeImpl = sinon.stub().returns( + Promise.resolve({ + json: () => { + return Promise.resolve(json); + }, + status: 200 + } as Response) + ); + initializeFetch(throwErrorFakeImpl); + const taskListQuery = getPostsRef(); + const onErrStub = sinon.stub(); + let unsubscribeFn: (() => void) | null = null; + const promise = new Promise((resolve, reject) => { + unsubscribeFn = subscribe(taskListQuery, { + onNext: () => { + resolve(null); + }, + onComplete: () => {}, + onErr: err => { + onErrStub(); + reject(err); + } + }); + }); + expect(onErrStub).not.to.have.been.called; + await expect(promise).to.have.eventually.been.rejected; + expect(onErrStub).to.have.been.calledOnce; + unsubscribeFn!(); + }); it('[QUERY] should retry auth whenever the fetcher returns with unauthorized', async () => { initializeFetch(fakeFetchImpl); const authProvider = new FakeAuthProvider(); From 7ff046672f380d58e1686084ed1a00936faa7ed8 Mon Sep 17 00:00:00 2001 From: Maneesh Tewani Date: Mon, 27 Oct 2025 14:12:23 -0700 Subject: [PATCH 2/8] Removed unnecessary changes --- packages/data-connect/src/core/QueryManager.ts | 1 - packages/data-connect/test/queries.test.ts | 3 --- 2 files changed, 4 deletions(-) diff --git a/packages/data-connect/src/core/QueryManager.ts b/packages/data-connect/src/core/QueryManager.ts index c1f3969dc22..f5521e0163c 100644 --- a/packages/data-connect/src/core/QueryManager.ts +++ b/packages/data-connect/src/core/QueryManager.ts @@ -118,7 +118,6 @@ export class QueryManager { }; const unsubscribe = (): void => { const trackedQuery = this._queries.get(key)!; - // TODO: Trigger onComplete trackedQuery.subscriptions = trackedQuery.subscriptions.filter( sub => sub !== subscription ); diff --git a/packages/data-connect/test/queries.test.ts b/packages/data-connect/test/queries.test.ts index fbee3707873..24db1e4508f 100644 --- a/packages/data-connect/test/queries.test.ts +++ b/packages/data-connect/test/queries.test.ts @@ -17,7 +17,6 @@ import { expect, use } from 'chai'; import chaiAsPromised from 'chai-as-promised'; -import sinonChai from 'sinon-chai'; import { connectDataConnectEmulator, @@ -39,7 +38,6 @@ import { import { getConnectionConfig, initDatabase, PROJECT_ID } from './util'; use(chaiAsPromised); -use(sinonChai); interface Post { id: string; @@ -130,7 +128,6 @@ describe('DataConnect Tests', async () => { }); expect(res.source).to.eq(SOURCE_SERVER); }); - it(`returns the result source as cache when data already exists`, async () => { const taskListQuery = getPostsRef(); const queryResult = await executeQuery(taskListQuery); From 874243bc13c4eb1f562df5679bb41327edaf055f Mon Sep 17 00:00:00 2001 From: Maneesh Tewani Date: Mon, 27 Oct 2025 14:13:44 -0700 Subject: [PATCH 3/8] Create spotty-shirts-design.md --- .changeset/spotty-shirts-design.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/spotty-shirts-design.md diff --git a/.changeset/spotty-shirts-design.md b/.changeset/spotty-shirts-design.md new file mode 100644 index 00000000000..77c6a8c8307 --- /dev/null +++ b/.changeset/spotty-shirts-design.md @@ -0,0 +1,5 @@ +--- +"@firebase/data-connect": patch +--- + +Fixed issue where onComplete wasn't triggering when the user calls `unsubscribe` on a subscription. From eb3fb0a41560d986a03dbceaa7adc6de1926919f Mon Sep 17 00:00:00 2001 From: Maneesh Tewani Date: Mon, 27 Oct 2025 14:49:38 -0700 Subject: [PATCH 4/8] Attempted to fix tests --- packages/data-connect/test/unit/queries.test.ts | 1 + scripts/emulator-testing/emulators/dataconnect-emulator.ts | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/data-connect/test/unit/queries.test.ts b/packages/data-connect/test/unit/queries.test.ts index 2bb12953370..513ed38c3fa 100644 --- a/packages/data-connect/test/unit/queries.test.ts +++ b/packages/data-connect/test/unit/queries.test.ts @@ -157,6 +157,7 @@ describe('Queries', () => { await expect(promise).to.have.eventually.been.rejected; expect(onErrStub).to.have.been.calledOnce; unsubscribeFn!(); + initializeFetch(globalThis.fetch); }); it('[QUERY] should retry auth whenever the fetcher returns with unauthorized', async () => { initializeFetch(fakeFetchImpl); diff --git a/scripts/emulator-testing/emulators/dataconnect-emulator.ts b/scripts/emulator-testing/emulators/dataconnect-emulator.ts index 9dc6add5df1..729889364d1 100644 --- a/scripts/emulator-testing/emulators/dataconnect-emulator.ts +++ b/scripts/emulator-testing/emulators/dataconnect-emulator.ts @@ -18,7 +18,7 @@ import { platform } from 'os'; import { Emulator } from './emulator'; -const DATACONNECT_EMULATOR_VERSION = '1.9.2'; +const DATACONNECT_EMULATOR_VERSION = '2.15.1'; export class DataConnectEmulator extends Emulator { constructor(port = 9399) { From 394d7caad8d16e066fc657ac7a3a0fddceeed7c4 Mon Sep 17 00:00:00 2001 From: Maneesh Tewani Date: Wed, 29 Oct 2025 09:27:43 -0700 Subject: [PATCH 5/8] Fixed docgen --- common/api-review/data-connect.api.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/common/api-review/data-connect.api.md b/common/api-review/data-connect.api.md index 9e3d2424876..61ad34336d5 100644 --- a/common/api-review/data-connect.api.md +++ b/common/api-review/data-connect.api.md @@ -114,6 +114,8 @@ export interface DataConnectSubscription { // (undocumented) errCallback?: (e?: DataConnectError) => void; // (undocumented) + onCompleteCallback?: () => void; + // (undocumented) unsubscribe: () => void; // (undocumented) userCallback: OnResultSubscription; From ee534ee5dc5aeba6be81b4167afc699194bb9c9b Mon Sep 17 00:00:00 2001 From: Maneesh Tewani Date: Wed, 29 Oct 2025 09:45:38 -0700 Subject: [PATCH 6/8] Fixed tests --- packages/data-connect/src/api/DataConnect.ts | 3 ++- packages/data-connect/src/api/query.ts | 2 +- packages/data-connect/src/core/AppCheckTokenProvider.ts | 6 +++--- packages/data-connect/src/core/QueryManager.ts | 4 ++-- packages/data-connect/src/network/fetch.ts | 6 +++--- packages/data-connect/src/network/transport/rest.ts | 4 ++-- packages/data-connect/src/util/validateArgs.ts | 2 +- packages/data-connect/tsconfig.json | 2 +- 8 files changed, 15 insertions(+), 14 deletions(-) diff --git a/packages/data-connect/src/api/DataConnect.ts b/packages/data-connect/src/api/DataConnect.ts index b7311363784..9d10b5dcc9f 100644 --- a/packages/data-connect/src/api/DataConnect.ts +++ b/packages/data-connect/src/api/DataConnect.ts @@ -198,6 +198,7 @@ export class DataConnect { // @internal enableEmulator(transportOptions: TransportOptions): void { if ( + this._transportOptions && this._initialized && !areTransportOptionsEqual(this._transportOptions, transportOptions) ) { @@ -314,7 +315,7 @@ export function validateDCOptions(dcOptions: ConnectorConfig): boolean { throw new DataConnectError(Code.INVALID_ARGUMENT, 'DC Option Required'); } fields.forEach(field => { - if (dcOptions[field] === null || dcOptions[field] === undefined) { + if (dcOptions[field as keyof ConnectorConfig] === null || dcOptions[field as keyof ConnectorConfig] === undefined) { throw new DataConnectError(Code.INVALID_ARGUMENT, `${field} Required`); } }); diff --git a/packages/data-connect/src/api/query.ts b/packages/data-connect/src/api/query.ts index 101a884bf6d..a6815c86d52 100644 --- a/packages/data-connect/src/api/query.ts +++ b/packages/data-connect/src/api/query.ts @@ -125,7 +125,7 @@ export function queryRef( dataConnect: dcInstance, refType: QUERY_STR, name: queryName, - variables + variables: variables as Variables }; } /** diff --git a/packages/data-connect/src/core/AppCheckTokenProvider.ts b/packages/data-connect/src/core/AppCheckTokenProvider.ts index 4b49a8f674a..615b45f3891 100644 --- a/packages/data-connect/src/core/AppCheckTokenProvider.ts +++ b/packages/data-connect/src/core/AppCheckTokenProvider.ts @@ -29,7 +29,7 @@ import { Provider } from '@firebase/component'; * Abstraction around AppCheck's token fetching capabilities. */ export class AppCheckTokenProvider { - private appCheck?: FirebaseAppCheckInternal; + private appCheck?: FirebaseAppCheckInternal | null; private serverAppAppCheckToken?: string; constructor( app: FirebaseApp, @@ -47,13 +47,13 @@ export class AppCheckTokenProvider { } } - getToken(): Promise { + getToken(): Promise { if (this.serverAppAppCheckToken) { return Promise.resolve({ token: this.serverAppAppCheckToken }); } if (!this.appCheck) { - return new Promise((resolve, reject) => { + return new Promise((resolve, reject) => { // Support delayed initialization of FirebaseAppCheck. This allows our // customers to initialize the RTDB SDK before initializing Firebase // AppCheck and ensures that all requests are authenticated if a token diff --git a/packages/data-connect/src/core/QueryManager.ts b/packages/data-connect/src/core/QueryManager.ts index f5521e0163c..977d96d9e72 100644 --- a/packages/data-connect/src/core/QueryManager.ts +++ b/packages/data-connect/src/core/QueryManager.ts @@ -98,7 +98,7 @@ export class QueryManager { addSubscription( queryRef: OperationRef, onResultCallback: OnResultSubscription, - onCompleteCallback: OnCompleteSubscription, + onCompleteCallback?: OnCompleteSubscription, onErrorCallback?: OnErrorSubscription, initialCache?: OpResult ): () => void { @@ -121,7 +121,7 @@ export class QueryManager { trackedQuery.subscriptions = trackedQuery.subscriptions.filter( sub => sub !== subscription ); - onCompleteCallback(); + onCompleteCallback?.(); }; if (initialCache && trackedQuery.currentCache !== initialCache) { logDebug('Initial cache found. Comparing dates.'); diff --git a/packages/data-connect/src/network/fetch.ts b/packages/data-connect/src/network/fetch.ts index 3e8e2cab476..62cef6fb220 100644 --- a/packages/data-connect/src/network/fetch.ts +++ b/packages/data-connect/src/network/fetch.ts @@ -56,9 +56,9 @@ export function dcFetch( url: string, body: DataConnectFetchBody, { signal }: AbortController, - appId: string | null, + appId: string | null | undefined, accessToken: string | null, - appCheckToken: string | null, + appCheckToken: string | null | undefined, _isUsingGen: boolean, _callerSdkType: CallerSdkType, _isUsingEmulator: boolean @@ -135,7 +135,7 @@ interface MessageObject { message?: string; } function getMessage(obj: MessageObject): string { - if ('message' in obj) { + if ('message' in obj && obj.message) { return obj.message; } return JSON.stringify(obj); diff --git a/packages/data-connect/src/network/transport/rest.ts b/packages/data-connect/src/network/transport/rest.ts index f16154dcb2a..4a3af8ac41a 100644 --- a/packages/data-connect/src/network/transport/rest.ts +++ b/packages/data-connect/src/network/transport/rest.ts @@ -34,7 +34,7 @@ export class RESTTransport implements DataConnectTransport { private _project = 'p'; private _serviceName: string; private _accessToken: string | null = null; - private _appCheckToken: string | null = null; + private _appCheckToken: string | null | undefined = null; private _lastToken: string | null = null; private _isUsingEmulator = false; constructor( @@ -106,7 +106,7 @@ export class RESTTransport implements DataConnectTransport { this._accessToken = newToken; } - async getWithAuth(forceToken = false): Promise { + async getWithAuth(forceToken = false): Promise { let starterPromise: Promise = new Promise(resolve => resolve(this._accessToken) ); diff --git a/packages/data-connect/src/util/validateArgs.ts b/packages/data-connect/src/util/validateArgs.ts index 15d1effa3da..e957786aa48 100644 --- a/packages/data-connect/src/util/validateArgs.ts +++ b/packages/data-connect/src/util/validateArgs.ts @@ -46,7 +46,7 @@ export function validateArgs( let realVars: Variables; if (dcOrVars && 'enableEmulator' in dcOrVars) { dcInstance = dcOrVars as DataConnect; - realVars = vars; + realVars = vars as Variables; } else { dcInstance = getDataConnect(connectorConfig); realVars = dcOrVars as Variables; diff --git a/packages/data-connect/tsconfig.json b/packages/data-connect/tsconfig.json index 58561f50f5d..f97ac9fe899 100644 --- a/packages/data-connect/tsconfig.json +++ b/packages/data-connect/tsconfig.json @@ -2,7 +2,7 @@ "extends": "../../config/tsconfig.base.json", "compilerOptions": { "outDir": "dist", - "strict": false + "strict": true }, "exclude": ["dist/**/*", "test/**/*"] } From 60b022d0f0c9642513bc3a048d5ff5c0b46807df Mon Sep 17 00:00:00 2001 From: Maneesh Tewani Date: Wed, 29 Oct 2025 09:47:29 -0700 Subject: [PATCH 7/8] Fixed test --- packages/data-connect/src/api/DataConnect.ts | 5 ++++- packages/data-connect/test/unit/queries.test.ts | 2 ++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/packages/data-connect/src/api/DataConnect.ts b/packages/data-connect/src/api/DataConnect.ts index 9d10b5dcc9f..15e713ba23b 100644 --- a/packages/data-connect/src/api/DataConnect.ts +++ b/packages/data-connect/src/api/DataConnect.ts @@ -315,7 +315,10 @@ export function validateDCOptions(dcOptions: ConnectorConfig): boolean { throw new DataConnectError(Code.INVALID_ARGUMENT, 'DC Option Required'); } fields.forEach(field => { - if (dcOptions[field as keyof ConnectorConfig] === null || dcOptions[field as keyof ConnectorConfig] === undefined) { + if ( + dcOptions[field as keyof ConnectorConfig] === null || + dcOptions[field as keyof ConnectorConfig] === undefined + ) { throw new DataConnectError(Code.INVALID_ARGUMENT, `${field} Required`); } }); diff --git a/packages/data-connect/test/unit/queries.test.ts b/packages/data-connect/test/unit/queries.test.ts index 513ed38c3fa..02d19bf856e 100644 --- a/packages/data-connect/test/unit/queries.test.ts +++ b/packages/data-connect/test/unit/queries.test.ts @@ -20,6 +20,7 @@ import { expect } from 'chai'; import * as chai from 'chai'; import chaiAsPromised from 'chai-as-promised'; import * as sinon from 'sinon'; +import sinonChai from 'sinon-chai'; import { DataConnectOptions, QueryRef, queryRef, subscribe } from '../../src'; import { @@ -30,6 +31,7 @@ import { initializeFetch } from '../../src/network/fetch'; import { RESTTransport } from '../../src/network/transport/rest'; import { initDatabase } from '../util'; chai.use(chaiAsPromised); +chai.use(sinonChai); const options: DataConnectOptions = { connector: 'c', location: 'l', From d73cb4c065ef85ccdda57dd641778889ab434564 Mon Sep 17 00:00:00 2001 From: Maneesh Tewani Date: Wed, 29 Oct 2025 11:04:39 -0700 Subject: [PATCH 8/8] Removed unnecessary doc --- common/api-review/data-connect.api.md | 12 ------------ packages/data-connect/src/api/query.ts | 9 --------- packages/data-connect/src/core/QueryManager.ts | 11 ++++++++++- 3 files changed, 10 insertions(+), 22 deletions(-) diff --git a/common/api-review/data-connect.api.md b/common/api-review/data-connect.api.md index 61ad34336d5..27a9d4af201 100644 --- a/common/api-review/data-connect.api.md +++ b/common/api-review/data-connect.api.md @@ -109,18 +109,6 @@ export interface DataConnectResult extends OpResult { ref: OperationRef; } -// @public -export interface DataConnectSubscription { - // (undocumented) - errCallback?: (e?: DataConnectError) => void; - // (undocumented) - onCompleteCallback?: () => void; - // (undocumented) - unsubscribe: () => void; - // (undocumented) - userCallback: OnResultSubscription; -} - // @public (undocumented) export type DataSource = typeof SOURCE_CACHE | typeof SOURCE_SERVER; diff --git a/packages/data-connect/src/api/query.ts b/packages/data-connect/src/api/query.ts index a6815c86d52..43683cafd63 100644 --- a/packages/data-connect/src/api/query.ts +++ b/packages/data-connect/src/api/query.ts @@ -39,15 +39,6 @@ export type OnErrorSubscription = (err?: DataConnectError) => void; * Signature for unsubscribe from `subscribe` */ export type QueryUnsubscribe = () => void; -/** - * Representation of user provided subscription options. - */ -export interface DataConnectSubscription { - userCallback: OnResultSubscription; - errCallback?: (e?: DataConnectError) => void; - onCompleteCallback?: () => void; - unsubscribe: () => void; -} /** * QueryRef object diff --git a/packages/data-connect/src/core/QueryManager.ts b/packages/data-connect/src/core/QueryManager.ts index 977d96d9e72..109f1d105b4 100644 --- a/packages/data-connect/src/core/QueryManager.ts +++ b/packages/data-connect/src/core/QueryManager.ts @@ -16,7 +16,6 @@ */ import { - DataConnectSubscription, OnCompleteSubscription, OnErrorSubscription, OnResultSubscription, @@ -40,6 +39,16 @@ import { setIfNotExists } from '../util/map'; import { Code, DataConnectError } from './error'; +/** + * Representation of user provided subscription options. + */ +interface DataConnectSubscription { + userCallback: OnResultSubscription; + errCallback?: (e?: DataConnectError) => void; + onCompleteCallback?: () => void; + unsubscribe: () => void; +} + interface TrackedQuery { ref: Omit, 'dataConnect'>; subscriptions: Array>;