Skip to content

Commit da1c7df

Browse files
authored
delete services that implement the new FirebaseService interface (#3601)
* delete services that implement the next FirebaseService interface in @firebase/component correctly * Create small-grapes-teach.md * debug CI issue * fix the delete loop * add eslint suppress * test * comment out imports * Revert "test" This reverts commit f1b0b44. * Revert "comment out imports" This reverts commit 57caf91. * Update small-grapes-teach.md * rename delete to _delete * revert prettier * change delete to _delete * fix test
1 parent 44c91e5 commit da1c7df

File tree

10 files changed

+81
-17
lines changed

10 files changed

+81
-17
lines changed

.changeset/small-grapes-teach.md

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@firebase/component": patch
3+
---
4+
5+
Correctly delete services created by modular SDKs when calling provider.delete()

packages-exp/app-exp/src/api.test.ts

+31-1
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,10 @@ import {
2929
onLog
3030
} from './api';
3131
import { DEFAULT_ENTRY_NAME } from './constants';
32-
import { _FirebaseAppInternal } from '@firebase/app-types-exp';
32+
import {
33+
_FirebaseAppInternal,
34+
_FirebaseService
35+
} from '@firebase/app-types-exp';
3336
import {
3437
_clearComponents,
3538
_components,
@@ -175,6 +178,33 @@ describe('API tests', () => {
175178
deleteApp(app).catch(() => {});
176179
expect(getApps().length).to.equal(0);
177180
});
181+
182+
it('waits for all services being deleted', async () => {
183+
_clearComponents();
184+
let count = 0;
185+
const comp1 = new Component(
186+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
187+
'test1' as any,
188+
_container =>
189+
({
190+
_delete: async () => {
191+
await Promise.resolve();
192+
expect(count).to.equal(0);
193+
count++;
194+
}
195+
} as _FirebaseService),
196+
ComponentType.PUBLIC
197+
);
198+
_registerComponent(comp1);
199+
200+
const app = initializeApp({});
201+
// create service instance
202+
const test1Provider = _getProvider(app, 'test1' as any);
203+
test1Provider.getImmediate();
204+
205+
await deleteApp(app);
206+
expect(count).to.equal(1);
207+
});
178208
});
179209

180210
describe('registerVersion', () => {

packages-exp/app-exp/test/util.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ export class TestService implements _FirebaseService {
2525
return this.app_;
2626
}
2727

28-
delete(): Promise<void> {
28+
_delete(): Promise<void> {
2929
return new Promise((resolve: (v?: void) => void) => {
3030
setTimeout(() => resolve(), 10);
3131
});

packages-exp/app-types-exp/index.d.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -113,7 +113,7 @@ export interface _FirebaseService {
113113
* Delete the service and free it's resources - called from
114114
* {@link @firebase/app-exp#deleteApp | deleteApp()}
115115
*/
116-
delete(): Promise<void>;
116+
_delete(): Promise<void>;
117117
}
118118

119119
export interface VersionService {

packages-exp/functions-exp/src/service.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,7 @@ export class FunctionsService implements _FirebaseService {
9595
});
9696
}
9797

98-
delete(): Promise<void> {
98+
_delete(): Promise<void> {
9999
return this.deleteService();
100100
}
101101

packages/component/src/provider.test.ts

+25-1
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import { expect } from 'chai';
1919
import { fake, SinonSpy } from 'sinon';
2020
import { ComponentContainer } from './component_container';
2121
import { FirebaseService } from '@firebase/app-types/private';
22+
import { _FirebaseService } from '@firebase/app-types-exp';
2223
import { Provider } from './provider';
2324
import { getFakeApp, getFakeComponent } from '../test/util';
2425
import '../test/setup';
@@ -202,7 +203,7 @@ describe('Provider', () => {
202203
});
203204

204205
describe('delete()', () => {
205-
it('calls delete() on the service instance that implements FirebaseService', () => {
206+
it('calls delete() on the service instance that implements legacy FirebaseService', () => {
206207
const deleteFake = fake();
207208
const myService: FirebaseService = {
208209
app: getFakeApp(),
@@ -226,6 +227,29 @@ describe('Provider', () => {
226227

227228
expect(deleteFake).to.have.been.called;
228229
});
230+
231+
it('calls delete() on the service instance that implements next FirebaseService', () => {
232+
const deleteFake = fake();
233+
const myService: _FirebaseService = {
234+
app: getFakeApp(),
235+
_delete: deleteFake
236+
};
237+
238+
// provide factory and create a service instance
239+
provider.setComponent(
240+
getFakeComponent(
241+
'test',
242+
() => myService,
243+
false,
244+
InstantiationMode.EAGER
245+
)
246+
);
247+
248+
// eslint-disable-next-line @typescript-eslint/no-floating-promises
249+
provider.delete();
250+
251+
expect(deleteFake).to.have.been.called;
252+
});
229253
});
230254

231255
describe('clearCache()', () => {

packages/component/src/provider.ts

+9-5
Original file line numberDiff line numberDiff line change
@@ -170,12 +170,16 @@ export class Provider<T extends Name> {
170170
async delete(): Promise<void> {
171171
const services = Array.from(this.instances.values());
172172

173-
await Promise.all(
174-
services
175-
.filter(service => 'INTERNAL' in service)
173+
await Promise.all([
174+
...services
175+
.filter(service => 'INTERNAL' in service) // legacy services
176176
// eslint-disable-next-line @typescript-eslint/no-explicit-any
177-
.map(service => (service as any).INTERNAL!.delete())
178-
);
177+
.map(service => (service as any).INTERNAL!.delete()),
178+
...services
179+
.filter(service => '_delete' in service) // modularized services
180+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
181+
.map(service => (service as any)._delete())
182+
]);
179183
}
180184

181185
isComponentSet(): boolean {

packages/firestore/exp/src/api/components.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ import {
2323
OfflineComponentProvider,
2424
OnlineComponentProvider
2525
} from '../../../src/core/component_provider';
26-
import {handleUserChange, LocalStore} from '../../../src/local/local_store';
26+
import { handleUserChange, LocalStore } from '../../../src/local/local_store';
2727
import { Deferred } from '../../../src/util/promise';
2828
import { logDebug } from '../../../src/util/log';
2929
import { SyncEngine } from '../../../src/core/sync_engine';
@@ -71,7 +71,7 @@ export async function setOfflineComponentProvider(
7171
// When a user calls clearPersistence() in one client, all other clients
7272
// need to be terminated to allow the delete to succeed.
7373
offlineComponentProvider.persistence.setDatabaseDeletedListener(() =>
74-
firestore.delete()
74+
firestore._delete()
7575
);
7676
offlineDeferred.resolve(offlineComponentProvider);
7777
}

packages/firestore/exp/src/api/database.ts

+3-2
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,8 @@ const LOG_TAG = 'Firestore';
6969
* The root reference to the Firestore database and the entry point for the
7070
* tree-shakeable SDK.
7171
*/
72-
export class Firestore extends LiteFirestore
72+
export class Firestore
73+
extends LiteFirestore
7374
implements firestore.FirebaseFirestore, _FirebaseService {
7475
readonly _queue = new AsyncQueue();
7576
readonly _persistenceKey: string;
@@ -320,7 +321,7 @@ export function terminate(
320321
): Promise<void> {
321322
_removeServiceInstance(firestore.app, 'firestore-exp');
322323
const firestoreImpl = cast(firestore, Firestore);
323-
return firestoreImpl.delete();
324+
return firestoreImpl._delete();
324325
}
325326

326327
function verifyNotInitialized(firestore: Firestore): void {

packages/firestore/lite/src/api/database.ts

+3-3
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,7 @@ export class Firestore
9595
return new DatabaseId(app.options.projectId!);
9696
}
9797

98-
delete(): Promise<void> {
98+
_delete(): Promise<void> {
9999
if (!this._terminateTask) {
100100
this._terminateTask = this._terminate();
101101
}
@@ -117,7 +117,7 @@ export class Firestore
117117
// TODO(firestoreexp): `deleteApp()` should call the delete method above,
118118
// but it still calls INTERNAL.delete().
119119
INTERNAL = {
120-
delete: () => this.delete()
120+
delete: () => this._delete()
121121
};
122122
}
123123

@@ -142,5 +142,5 @@ export function terminate(
142142
): Promise<void> {
143143
_removeServiceInstance(firestore.app, 'firestore/lite');
144144
const firestoreClient = cast(firestore, Firestore);
145-
return firestoreClient.delete();
145+
return firestoreClient._delete();
146146
}

0 commit comments

Comments
 (0)