From 519cc5894ef36d67487aa240593455a1cda8370d Mon Sep 17 00:00:00 2001 From: Charles Fries Date: Mon, 22 Apr 2024 11:11:02 -0700 Subject: [PATCH 1/2] Generics --- addon/adapters/cloud-firestore-modular.ts | 42 ++++++++++--------- addon/services/-firestore-data-manager.ts | 37 +++++++++------- .../services/-firestore-data-manager-test.ts | 12 +++--- 3 files changed, 50 insertions(+), 41 deletions(-) diff --git a/addon/adapters/cloud-firestore-modular.ts b/addon/adapters/cloud-firestore-modular.ts index 022e44d..4c2a627 100644 --- a/addon/adapters/cloud-firestore-modular.ts +++ b/addon/adapters/cloud-firestore-modular.ts @@ -8,6 +8,7 @@ import { getOwner } from '@ember/application'; import { inject as service } from '@ember/service'; import Adapter from '@ember-data/adapter'; import DS from 'ember-data'; +import ModelRegistry from 'ember-data/types/registries/model'; import RSVP from 'rsvp'; import Store from '@ember-data/store'; @@ -34,9 +35,9 @@ import FirestoreDataManager from 'ember-cloud-firestore-adapter/services/-firest import buildCollectionName from 'ember-cloud-firestore-adapter/-private/build-collection-name'; import flattenDocSnapshot from 'ember-cloud-firestore-adapter/-private/flatten-doc-snapshot'; -interface ModelClass { - modelName: string; -} +type ModelClass = ModelRegistry[K] & { + modelName: K; +}; interface AdapterOption { isRealtime?: boolean; @@ -53,12 +54,12 @@ interface Snapshot extends DS.Snapshot { adapterOptions: AdapterOption; } -interface SnapshotRecordArray extends DS.SnapshotRecordArray { +interface SnapshotRecordArray extends DS.SnapshotRecordArray { adapterOptions: AdapterOption; } interface BelongsToRelationshipMeta { - type: string; + type: keyof ModelRegistry; options: { isRealtime?: boolean }; } @@ -85,6 +86,7 @@ export default class CloudFirestoreModularAdapter extends Adapter { return fastboot && fastboot.isFastBoot; } + // @ts-expect-error EmberData types are incorrect public generateIdForRecord(_store: Store, type: string): string { const db = getFirestore(); const collectionName = buildCollectionName(type); @@ -92,17 +94,17 @@ export default class CloudFirestoreModularAdapter extends Adapter { return doc(collection(db, collectionName)).id; } - public createRecord( + public createRecord( store: Store, - type: ModelClass, + type: ModelClass, snapshot: Snapshot, ): RSVP.Promise { return this.updateRecord(store, type, snapshot); } - public updateRecord( + public updateRecord( _store: Store, - type: ModelClass, + type: ModelClass, snapshot: Snapshot, ): RSVP.Promise { return new RSVP.Promise((resolve, reject) => { @@ -125,9 +127,9 @@ export default class CloudFirestoreModularAdapter extends Adapter { }); } - public deleteRecord( + public deleteRecord( _store: Store, - type: ModelClass, + type: ModelClass, snapshot: Snapshot, ): RSVP.Promise { return new RSVP.Promise((resolve, reject) => { @@ -147,9 +149,9 @@ export default class CloudFirestoreModularAdapter extends Adapter { }); } - public findRecord( + public findRecord( _store: Store, - type: ModelClass, + type: ModelClass, id: string, snapshot: Snapshot, ): RSVP.Promise { @@ -172,9 +174,9 @@ export default class CloudFirestoreModularAdapter extends Adapter { }); } - public findAll( + public findAll( _store: Store, - type: ModelClass, + type: ModelClass, _sinceToken: string, snapshotRecordArray?: SnapshotRecordArray, ): RSVP.Promise { @@ -195,9 +197,9 @@ export default class CloudFirestoreModularAdapter extends Adapter { }); } - public query( + public query( _store: Store, - type: ModelClass, + type: ModelClass, queryOption: AdapterOption, recordArray: DS.AdapterPopulatedRecordArray, ): RSVP.Promise { @@ -267,7 +269,7 @@ export default class CloudFirestoreModularAdapter extends Adapter { const queryRef = this.buildHasManyCollectionRef(store, snapshot, url, relationship); const config = { queryRef, - modelName: snapshot.modelName as string, + modelName: snapshot.modelName, id: snapshot.id, field: relationship.key, referenceKeyName: this.referenceKeyName, @@ -334,10 +336,10 @@ export default class CloudFirestoreModularAdapter extends Adapter { return relationship.options.filter?.(collectionRef, snapshot.record) || collectionRef; } - const cardinality = snapshot.type.determineRelationshipType(relationship, store); + const cardinality = (snapshot.type as any).determineRelationshipType(relationship, store); if (cardinality === 'manyToOne') { - const inverse = snapshot.type.inverseFor(relationship.key, store); + const inverse = (snapshot.type as any).inverseFor(relationship.key, store); const snapshotCollectionName = buildCollectionName(snapshot.modelName.toString()); const snapshotDocRef = doc(db, `${snapshotCollectionName}/${snapshot.id}`); const collectionRef = collection(db, url); diff --git a/addon/services/-firestore-data-manager.ts b/addon/services/-firestore-data-manager.ts index effda9e..4d859ce 100644 --- a/addon/services/-firestore-data-manager.ts +++ b/addon/services/-firestore-data-manager.ts @@ -1,6 +1,11 @@ +/* + eslint + ember/use-ember-data-rfc-395-imports: off +*/ + import { next } from '@ember/runloop'; -// eslint-disable-next-line ember/use-ember-data-rfc-395-imports import DS from 'ember-data'; +import ModelRegistry from 'ember-data/types/registries/model'; import Service, { inject as service } from '@ember/service'; import StoreService from '@ember-data/store'; @@ -38,7 +43,7 @@ interface QueryListeners { } interface QueryFetchConfig { - modelName: string; + modelName: keyof ModelRegistry; referenceKeyName: string; recordArray: DS.AdapterPopulatedRecordArray; queryRef: Query, @@ -46,7 +51,7 @@ interface QueryFetchConfig { } interface HasManyFetchConfig { - modelName: string; + modelName: keyof ModelRegistry; id: string; field: string; referenceKeyName: string; @@ -75,7 +80,7 @@ export default class FirestoreDataManager extends Service { } public async findRecordRealtime( - modelName: string, + modelName: keyof ModelRegistry, docRef: DocumentReference, ): Promise { const { path: listenerKey } = docRef; @@ -88,7 +93,7 @@ export default class FirestoreDataManager extends Service { } public async findAllRealtime( - modelName: string, + modelName: keyof ModelRegistry, colRef: CollectionReference, ): Promise { const { path: listenerKey } = colRef; @@ -143,14 +148,14 @@ export default class FirestoreDataManager extends Service { ): Promise { const querySnapshot = await getDocs(queryRef); const promises = querySnapshot.docs.map((docSnapshot) => ( - this.getReferenceToDoc(docSnapshot, '', referenceKey) + this.getReferenceToDoc(docSnapshot, '' as keyof ModelRegistry, referenceKey) )); return Promise.all(promises); } private setupDocRealtimeUpdates( - modelName: string, + modelName: keyof ModelRegistry, docRef: DocumentReference, ): Promise { return new Promise((resolve, reject) => { @@ -171,7 +176,7 @@ export default class FirestoreDataManager extends Service { } private setupColRealtimeUpdates( - modelName: string, + modelName: keyof ModelRegistry, colRef: CollectionReference, ): Promise { return new Promise((resolve, reject) => { @@ -251,7 +256,7 @@ export default class FirestoreDataManager extends Service { private handleSubsequentDocRealtimeUpdates( docSnapshot: DocumentSnapshot, - modelName: string, + modelName: keyof ModelRegistry, listenerKey: string, ): void { if (docSnapshot.exists()) { @@ -263,7 +268,7 @@ export default class FirestoreDataManager extends Service { } private handleSubsequentColRealtimeUpdates( - modelName: string, + modelName: keyof ModelRegistry, listenerKey: string, querySnapshot: QuerySnapshot, ): void { @@ -339,15 +344,17 @@ export default class FirestoreDataManager extends Service { // To avoid the issue, we run .reload() in the next runloop so that we allow the unload // to happen first. next(() => { - const hasManyRef = this.store.peekRecord(config.modelName, config.id).hasMany(config.field); + const hasManyRef = this.store.peekRecord(config.modelName, config.id)?.hasMany( + config.field as never, + ); - hasManyRef.reload(); + hasManyRef?.reload(); }); } public async getReferenceToDoc( docSnapshot: DocumentSnapshot, - modelName: string, + modelName: keyof ModelRegistry, referenceKeyName: string, isRealtime = false, ): Promise { @@ -360,7 +367,7 @@ export default class FirestoreDataManager extends Service { return docSnapshot; } - private pushRecord(modelName: string, snapshot: DocumentSnapshot): void { + private pushRecord(modelName: keyof ModelRegistry, snapshot: DocumentSnapshot): void { const flatRecord = flattenDocSnapshot(snapshot); const normalizedRecord = this.store.normalize(modelName, flatRecord); @@ -373,7 +380,7 @@ export default class FirestoreDataManager extends Service { } } - private unloadRecord(modelName: string, id: string, path?: string): void { + private unloadRecord(modelName: keyof ModelRegistry, id: string, path?: string): void { const record = this.store.peekRecord(modelName, id); if (record !== null) { diff --git a/tests/unit/services/-firestore-data-manager-test.ts b/tests/unit/services/-firestore-data-manager-test.ts index 6a56cf1..7a5ee56 100644 --- a/tests/unit/services/-firestore-data-manager-test.ts +++ b/tests/unit/services/-firestore-data-manager-test.ts @@ -155,7 +155,7 @@ module('Unit | Service | -firestore-data-manager', function (hooks) { const queryRef = query(colRef); const config = { queryRef, - modelName: 'user', + modelName: 'user' as const, referenceKeyName: 'referenceTo', recordArray: { update: () => DS.PromiseArray.create({ promise: RSVP.Promise.resolve([]) }), @@ -180,7 +180,7 @@ module('Unit | Service | -firestore-data-manager', function (hooks) { const queryRef = query(colRef); const config = { queryRef, - modelName: 'user', + modelName: 'user' as const, referenceKeyName: 'referenceTo', recordArray: { update: () => DS.PromiseArray.create({ promise: RSVP.Promise.resolve([]) }), @@ -206,7 +206,7 @@ module('Unit | Service | -firestore-data-manager', function (hooks) { const queryRef = query(colRef); const config = { queryRef, - modelName: 'user', + modelName: 'user' as const, referenceKeyName: 'referenceTo', queryId: 'test', recordArray: { @@ -240,7 +240,7 @@ module('Unit | Service | -firestore-data-manager', function (hooks) { const queryRef = query(colRef); const config = { queryRef, - modelName: 'user', + modelName: 'user' as const, id: 'user_a', field: 'posts', referenceKeyName: 'referenceTo', @@ -264,7 +264,7 @@ module('Unit | Service | -firestore-data-manager', function (hooks) { const queryRef = query(colRef); const config = { queryRef, - modelName: 'user', + modelName: 'user' as const, id: 'user_a', field: 'groups', referenceKeyName: 'referenceTo', @@ -296,7 +296,7 @@ module('Unit | Service | -firestore-data-manager', function (hooks) { const queryRef = query(colRef); const config = { queryRef, - modelName: 'user', + modelName: 'user' as const, id: 'user_a', field: 'groups', referenceKeyName: 'referenceTo', From 2cdfcdd62bf324d4d1bc20644313bd30fb0b1f36 Mon Sep 17 00:00:00 2001 From: Charles Fries Date: Mon, 22 Apr 2024 13:58:43 -0700 Subject: [PATCH 2/2] Remove comment --- addon/adapters/cloud-firestore-modular.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/addon/adapters/cloud-firestore-modular.ts b/addon/adapters/cloud-firestore-modular.ts index 4c2a627..705d853 100644 --- a/addon/adapters/cloud-firestore-modular.ts +++ b/addon/adapters/cloud-firestore-modular.ts @@ -86,7 +86,6 @@ export default class CloudFirestoreModularAdapter extends Adapter { return fastboot && fastboot.isFastBoot; } - // @ts-expect-error EmberData types are incorrect public generateIdForRecord(_store: Store, type: string): string { const db = getFirestore(); const collectionName = buildCollectionName(type);