diff --git a/.eslintrc.js b/.eslintrc.js index d3da7c99..eda795b2 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -9,8 +9,9 @@ module.exports = { plugins: ['ember', '@typescript-eslint'], extends: [ 'airbnb-base', - 'plugin:@typescript-eslint/recommended', + 'eslint:recommended', 'plugin:ember/recommended', + 'plugin:prettier/recommended', ], env: { browser: true, @@ -49,6 +50,15 @@ module.exports = { 'func-names': 'off', }, overrides: [ + // ts files + { + files: ['**/*.ts'], + extends: [ + 'plugin:@typescript-eslint/eslint-recommended', + 'plugin:@typescript-eslint/recommended', + ], + rules: {}, + }, // node files { files: [ @@ -72,7 +82,6 @@ module.exports = { }, extends: ['plugin:n/recommended'], rules: { - '@typescript-eslint/no-var-requires': 'off', 'global-require': 'off', 'prefer-object-spread': 'off', 'prefer-rest-params': 'off', @@ -86,8 +95,6 @@ module.exports = { extends: ['plugin:qunit/recommended'], rules: { 'prefer-arrow-callback': 'off', - 'func-names': 'off', - '@typescript-eslint/no-empty-function': 'off', 'qunit/require-expect': 'off', }, }, diff --git a/addon/-private/flatten-doc-snapshot.ts b/addon/-private/flatten-doc-snapshot.ts index 17674cd2..093db73a 100644 --- a/addon/-private/flatten-doc-snapshot.ts +++ b/addon/-private/flatten-doc-snapshot.ts @@ -1,8 +1,8 @@ import { DocumentSnapshot } from 'firebase/firestore'; export default function flattenDocSnapshot(docSnapshot: DocumentSnapshot): { - id: string, - [key: string]: unknown, + id: string; + [key: string]: unknown; } { const { id } = docSnapshot; const data = docSnapshot.data() || {}; diff --git a/addon/adapters/cloud-firestore-modular.ts b/addon/adapters/cloud-firestore-modular.ts index 88747a5c..fa3c6493 100644 --- a/addon/adapters/cloud-firestore-modular.ts +++ b/addon/adapters/cloud-firestore-modular.ts @@ -44,7 +44,8 @@ interface Snapshot extends DS.Snapshot { adapterOptions: AdapterOption; } -interface SnapshotRecordArray extends DS.SnapshotRecordArray { +interface SnapshotRecordArray + extends DS.SnapshotRecordArray { adapterOptions: AdapterOption; } @@ -57,10 +58,10 @@ interface HasManyRelationshipMeta { key: string; type: string; options: { - isRealtime?: boolean, + isRealtime?: boolean; - buildReference?(db: Firestore, record: unknown): CollectionReference, - filter?(db: CollectionReference | Query, record: unknown): Query, + buildReference?(db: Firestore, record: unknown): CollectionReference; + filter?(db: CollectionReference | Query, record: unknown): Query; }; } @@ -98,22 +99,31 @@ export default class CloudFirestoreAdapter extends Adapter { snapshot: Snapshot, ): RSVP.Promise { return new RSVP.Promise((resolve, reject) => { - const collectionRef = this.buildCollectionRef(type.modelName, snapshot.adapterOptions); + const collectionRef = this.buildCollectionRef( + type.modelName, + snapshot.adapterOptions, + ); const docRef = doc(collectionRef, snapshot.id); const batch = this.buildWriteBatch(docRef, snapshot); - batch.commit().then(() => { - const data = this.serialize(snapshot, { includeId: true }); - - resolve(data); - - if (snapshot.adapterOptions?.isRealtime && !this.isFastBoot) { - // Setup realtime listener for record - this.firestoreDataManager.findRecordRealtime(type.modelName, docRef); - } - }).catch((e) => { - reject(e); - }); + batch + .commit() + .then(() => { + const data = this.serialize(snapshot, { includeId: true }); + + resolve(data); + + if (snapshot.adapterOptions?.isRealtime && !this.isFastBoot) { + // Setup realtime listener for record + this.firestoreDataManager.findRecordRealtime( + type.modelName, + docRef, + ); + } + }) + .catch((e) => { + reject(e); + }); }); } @@ -124,18 +134,24 @@ export default class CloudFirestoreAdapter extends Adapter { ): RSVP.Promise { return new RSVP.Promise((resolve, reject) => { const db = getFirestore(); - const collectionRef = this.buildCollectionRef(type.modelName, snapshot.adapterOptions); + const collectionRef = this.buildCollectionRef( + type.modelName, + snapshot.adapterOptions, + ); const docRef = doc(collectionRef, snapshot.id); const batch = writeBatch(db); batch.delete(docRef); this.addIncludeToWriteBatch(batch, snapshot.adapterOptions); - batch.commit().then(() => { - resolve(); - }).catch((e) => { - reject(e); - }); + batch + .commit() + .then(() => { + resolve(); + }) + .catch((e) => { + reject(e); + }); }); } @@ -147,16 +163,27 @@ export default class CloudFirestoreAdapter extends Adapter { ): RSVP.Promise { return new RSVP.Promise(async (resolve, reject) => { try { - const colRef = this.buildCollectionRef(type.modelName, snapshot.adapterOptions); + const colRef = this.buildCollectionRef( + type.modelName, + snapshot.adapterOptions, + ); const docRef = doc(colRef, id); - const docSnapshot = snapshot.adapterOptions?.isRealtime && !this.isFastBoot - ? await this.firestoreDataManager.findRecordRealtime(type.modelName, docRef) - : await getDoc(docRef); + const docSnapshot = + snapshot.adapterOptions?.isRealtime && !this.isFastBoot + ? await this.firestoreDataManager.findRecordRealtime( + type.modelName, + docRef, + ) + : await getDoc(docRef); if (docSnapshot.exists()) { resolve(flattenDocSnapshot(docSnapshot)); } else { - reject(new AdapterRecordNotFoundError(`Record ${id} for model type ${type.modelName} doesn't exist`)); + reject( + new AdapterRecordNotFoundError( + `Record ${id} for model type ${type.modelName} doesn't exist`, + ), + ); } } catch (error) { reject(error); @@ -173,12 +200,21 @@ export default class CloudFirestoreAdapter extends Adapter { return new RSVP.Promise(async (resolve, reject) => { try { const db = getFirestore(); - const colRef = collection(db, buildCollectionName(type.modelName as string)); - const querySnapshot = snapshotRecordArray?.adapterOptions?.isRealtime && !this.isFastBoot - ? await this.firestoreDataManager.findAllRealtime(type.modelName, colRef) - : await getDocs(colRef); - - const result = querySnapshot.docs.map((docSnapshot) => flattenDocSnapshot(docSnapshot)); + const colRef = collection( + db, + buildCollectionName(type.modelName as string), + ); + const querySnapshot = + snapshotRecordArray?.adapterOptions?.isRealtime && !this.isFastBoot + ? await this.firestoreDataManager.findAllRealtime( + type.modelName, + colRef, + ) + : await getDocs(colRef); + + const result = querySnapshot.docs.map((docSnapshot) => + flattenDocSnapshot(docSnapshot), + ); resolve(result); } catch (error) { @@ -204,11 +240,17 @@ export default class CloudFirestoreAdapter extends Adapter { referenceKeyName: this.referenceKeyName, queryId: queryOption.queryId, }; - const docSnapshots = queryOption.isRealtime && !this.isFastBoot - ? await this.firestoreDataManager.queryRealtime(config) - : await this.firestoreDataManager.queryWithReferenceTo(queryRef, this.referenceKeyName); - - const result = docSnapshots.map((docSnapshot) => (flattenDocSnapshot(docSnapshot))); + const docSnapshots = + queryOption.isRealtime && !this.isFastBoot + ? await this.firestoreDataManager.queryRealtime(config) + : await this.firestoreDataManager.queryWithReferenceTo( + queryRef, + this.referenceKeyName, + ); + + const result = docSnapshots.map((docSnapshot) => + flattenDocSnapshot(docSnapshot), + ); resolve(result); } catch (error) { @@ -233,14 +275,22 @@ export default class CloudFirestoreAdapter extends Adapter { const db = getFirestore(); const docRef = doc(db, urlNodes.join('/'), id); const modelName = relationship.type; - const docSnapshot = relationship.options.isRealtime && !this.isFastBoot - ? await this.firestoreDataManager.findRecordRealtime(modelName, docRef) - : await getDoc(docRef); + const docSnapshot = + relationship.options.isRealtime && !this.isFastBoot + ? await this.firestoreDataManager.findRecordRealtime( + modelName, + docRef, + ) + : await getDoc(docRef); if (docSnapshot.exists()) { resolve(flattenDocSnapshot(docSnapshot)); } else { - reject(new AdapterRecordNotFoundError(`Record ${id} for model type ${modelName} doesn't exist`)); + reject( + new AdapterRecordNotFoundError( + `Record ${id} for model type ${modelName} doesn't exist`, + ), + ); } } catch (error) { reject(error); @@ -256,7 +306,12 @@ export default class CloudFirestoreAdapter extends Adapter { ): RSVP.Promise { return new RSVP.Promise(async (resolve, reject) => { try { - const queryRef = this.buildHasManyCollectionRef(store, snapshot, url, relationship); + const queryRef = this.buildHasManyCollectionRef( + store, + snapshot, + url, + relationship, + ); const config = { queryRef, modelName: snapshot.modelName, @@ -264,11 +319,17 @@ export default class CloudFirestoreAdapter extends Adapter { field: relationship.key, referenceKeyName: this.referenceKeyName, }; - const documentSnapshots = relationship.options.isRealtime && !this.isFastBoot - ? await this.firestoreDataManager.findHasManyRealtime(config) - : await this.firestoreDataManager.queryWithReferenceTo(queryRef, this.referenceKeyName); - - const result = documentSnapshots.map((docSnapshot) => (flattenDocSnapshot(docSnapshot))); + const documentSnapshots = + relationship.options.isRealtime && !this.isFastBoot + ? await this.firestoreDataManager.findHasManyRealtime(config) + : await this.firestoreDataManager.queryWithReferenceTo( + queryRef, + this.referenceKeyName, + ); + + const result = documentSnapshots.map((docSnapshot) => + flattenDocSnapshot(docSnapshot), + ); resolve(result); } catch (error) { @@ -283,8 +344,10 @@ export default class CloudFirestoreAdapter extends Adapter { ): CollectionReference { const db = getFirestore(); - return adapterOptions?.buildReference?.(db) - || collection(db, buildCollectionName(modelName as string)); + return ( + adapterOptions?.buildReference?.(db) || + collection(db, buildCollectionName(modelName as string)) + ); } private addDocRefToWriteBatch( @@ -297,13 +360,19 @@ export default class CloudFirestoreAdapter extends Adapter { batch.set(docRef, data, { merge: true }); } - private addIncludeToWriteBatch(batch: WriteBatch, adapterOptions?: AdapterOption): void { + private addIncludeToWriteBatch( + batch: WriteBatch, + adapterOptions?: AdapterOption, + ): void { const db = getFirestore(); adapterOptions?.include?.(batch, db); } - private buildWriteBatch(docRef: DocumentReference, snapshot: Snapshot): WriteBatch { + private buildWriteBatch( + docRef: DocumentReference, + snapshot: Snapshot, + ): WriteBatch { const db = getFirestore(); const batch = writeBatch(db); @@ -322,27 +391,49 @@ export default class CloudFirestoreAdapter extends Adapter { const db = getFirestore(); if (relationship.options.buildReference) { - const collectionRef = relationship.options.buildReference(db, snapshot.record); - - return relationship.options.filter?.(collectionRef, snapshot.record) || collectionRef; + const collectionRef = relationship.options.buildReference( + db, + snapshot.record, + ); + + return ( + relationship.options.filter?.(collectionRef, snapshot.record) || + collectionRef + ); } const modelClass = store.modelFor(snapshot.modelName); - const cardinality = modelClass.determineRelationshipType(relationship, store); + const cardinality = modelClass.determineRelationshipType( + relationship, + store, + ); if (cardinality === 'manyToOne') { const inverse = modelClass.inverseFor(relationship.key, store); - const snapshotCollectionName = buildCollectionName(snapshot.modelName.toString()); - const snapshotDocRef = doc(db, `${snapshotCollectionName}/${snapshot.id}`); + const snapshotCollectionName = buildCollectionName( + snapshot.modelName.toString(), + ); + const snapshotDocRef = doc( + db, + `${snapshotCollectionName}/${snapshot.id}`, + ); const collectionRef = collection(db, url); - const queryRef = query(collectionRef, where(inverse.name, '==', snapshotDocRef)); - - return relationship.options.filter?.(queryRef, snapshot.record) || queryRef; + const queryRef = query( + collectionRef, + where(inverse.name, '==', snapshotDocRef), + ); + + return ( + relationship.options.filter?.(queryRef, snapshot.record) || queryRef + ); } const collectionRef = collection(db, url); - return relationship.options.filter?.(collectionRef, snapshot.record) || collectionRef; + return ( + relationship.options.filter?.(collectionRef, snapshot.record) || + collectionRef + ); } } diff --git a/addon/authenticators/firebase.ts b/addon/authenticators/firebase.ts index 257e87df..197a64aa 100644 --- a/addon/authenticators/firebase.ts +++ b/addon/authenticators/firebase.ts @@ -21,7 +21,9 @@ export default class FirebaseAuthenticator extends BaseAuthenticator { return getOwner(this)?.lookup('service:fastboot'); } - public async authenticate(callback: AuthenticateCallback): Promise<{ user: User | null }> { + public async authenticate( + callback: AuthenticateCallback, + ): Promise<{ user: User | null }> { const auth = getAuth(); const credential = await callback(auth); @@ -39,41 +41,53 @@ export default class FirebaseAuthenticator extends BaseAuthenticator { const auth = getAuth(); if ( - this.fastboot?.isFastBoot - && this.fastboot.request.headers.get('Authorization')?.startsWith('Bearer ') + this.fastboot?.isFastBoot && + this.fastboot.request.headers + .get('Authorization') + ?.startsWith('Bearer ') ) { - const token = this.fastboot.request.headers.get('Authorization')?.split('Bearer ')[1]; + const token = this.fastboot.request.headers + .get('Authorization') + ?.split('Bearer ')[1]; if (token) { - signInWithCustomToken(auth, token).then((credential) => { - resolve({ user: credential.user }); - }).catch(() => { - reject(); - }); + signInWithCustomToken(auth, token) + .then((credential) => { + resolve({ user: credential.user }); + }) + .catch(() => { + reject(); + }); } else { reject(); } } else { - const unsubscribe = onAuthStateChanged(auth, async (user) => { - unsubscribe(); + const unsubscribe = onAuthStateChanged( + auth, + async (user) => { + unsubscribe(); - if (user) { - resolve({ user }); - } else { - getRedirectResult(auth).then((credential) => { - if (credential) { - resolve({ user: credential.user }); - } else { - reject(); - } - }).catch(() => { - reject(); - }); - } - }, () => { - reject(); - unsubscribe(); - }); + if (user) { + resolve({ user }); + } else { + getRedirectResult(auth) + .then((credential) => { + if (credential) { + resolve({ user: credential.user }); + } else { + reject(); + } + }) + .catch(() => { + reject(); + }); + } + }, + () => { + reject(); + unsubscribe(); + }, + ); } }); } diff --git a/addon/instance-initializers/firebase-settings.ts b/addon/instance-initializers/firebase-settings.ts index 3d633482..60be5476 100644 --- a/addon/instance-initializers/firebase-settings.ts +++ b/addon/instance-initializers/firebase-settings.ts @@ -4,49 +4,62 @@ import { FirebaseApp, FirebaseOptions } from 'firebase/app'; import { Firestore, EmulatorMockTokenOptions } from 'firebase/firestore'; import { initializeApp } from 'ember-cloud-firestore-adapter/firebase/app'; -import { connectFirestoreEmulator, getFirestore, initializeFirestore } from 'ember-cloud-firestore-adapter/firebase/firestore'; -import { connectAuthEmulator, getAuth } from 'ember-cloud-firestore-adapter/firebase/auth'; -import { connectFunctionsEmulator, getFunctions } from 'ember-cloud-firestore-adapter/firebase/functions'; -import { connectStorageEmulator, getStorage } from 'ember-cloud-firestore-adapter/firebase/storage'; +import { + connectFirestoreEmulator, + getFirestore, + initializeFirestore, +} from 'ember-cloud-firestore-adapter/firebase/firestore'; +import { + connectAuthEmulator, + getAuth, +} from 'ember-cloud-firestore-adapter/firebase/auth'; +import { + connectFunctionsEmulator, + getFunctions, +} from 'ember-cloud-firestore-adapter/firebase/functions'; +import { + connectStorageEmulator, + getStorage, +} from 'ember-cloud-firestore-adapter/firebase/storage'; interface FirestoreAddonConfig { isCustomSetup?: boolean; settings?: { [key: string]: string }; emulator?: { - hostname: string, - port: number, - options?: { [key: string]: unknown } + hostname: string; + port: number; + options?: { [key: string]: unknown }; }; } interface AuthAddonConfig { isCustomSetup?: boolean; emulator?: { - hostname: string, - port: number, - options?: { disableWarnings: boolean } + hostname: string; + port: number; + options?: { disableWarnings: boolean }; }; } interface FunctionsAddonConfig { isCustomSetup?: boolean; emulator?: { - hostname: string, - port: number, + hostname: string; + port: number; }; } interface StorageAddonConfig { isCustomSetup?: boolean; emulator?: { - hostname: string, - port: number, - options?: { mockUserToken?: EmulatorMockTokenOptions | string } + hostname: string; + port: number; + options?: { mockUserToken?: EmulatorMockTokenOptions | string }; }; } interface AddonConfig { - firebaseConfig: FirebaseOptions, + firebaseConfig: FirebaseOptions; firestore?: FirestoreAddonConfig; auth?: AuthAddonConfig; functions?: FunctionsAddonConfig; @@ -120,14 +133,18 @@ function setupModularInstance(config: AddonConfig) { } export function initialize(appInstance: ApplicationInstance): void { - const config = appInstance.resolveRegistration('config:environment') as Config; + const config = appInstance.resolveRegistration( + 'config:environment', + ) as Config; const addonConfig = config['ember-cloud-firestore-adapter']; try { setupModularInstance(addonConfig); } catch (e) { if (e.code !== 'failed-precondition') { - throw new Error(`There was a problem with initializing Firebase. Check if you've configured the addon properly. | Error: ${e}`); + throw new Error( + `There was a problem with initializing Firebase. Check if you've configured the addon properly. | Error: ${e}`, + ); } } } diff --git a/addon/serializers/cloud-firestore-modular.ts b/addon/serializers/cloud-firestore-modular.ts index 97c34f4c..10ea0fca 100644 --- a/addon/serializers/cloud-firestore-modular.ts +++ b/addon/serializers/cloud-firestore-modular.ts @@ -8,9 +8,16 @@ import DS, { ModelSchema } from 'ember-data'; import JSONSerializer from '@ember-data/serializer/json'; import Store from '@ember-data/store'; -import { CollectionReference, DocumentReference, Firestore } from 'firebase/firestore'; - -import { doc, getFirestore } from 'ember-cloud-firestore-adapter/firebase/firestore'; +import { + CollectionReference, + DocumentReference, + Firestore, +} from 'firebase/firestore'; + +import { + doc, + getFirestore, +} from 'ember-cloud-firestore-adapter/firebase/firestore'; import buildCollectionName from 'ember-cloud-firestore-adapter/-private/build-collection-name'; interface Links { @@ -27,19 +34,22 @@ interface RelationshipDefinition { key: string; type: string; options: { - buildReference?(db: Firestore, record: unknown): CollectionReference + buildReference?(db: Firestore, record: unknown): CollectionReference; }; } type ModelClass = ModelSchema & { - determineRelationshipType(descriptor: { kind: string, type: string }, store: Store): string; -} + determineRelationshipType( + descriptor: { kind: string; type: string }, + store: Store, + ): string; +}; export default class CloudFirestoreSerializer extends JSONSerializer { public extractRelationship( relationshipModelName: string, relationshipHash: DocumentReference, - ): { id: string, type: string } | Record { + ): { id: string; type: string } | Record { if (isNone(relationshipHash)) { return super.extractRelationship(relationshipModelName, relationshipHash); } @@ -65,13 +75,18 @@ export default class CloudFirestoreSerializer extends JSONSerializer { links[name] = data.path; } } else { - const cardinality = modelClass.determineRelationshipType(descriptor, this.store); + const cardinality = modelClass.determineRelationshipType( + descriptor, + this.store, + ); let hasManyPath; if (cardinality === 'manyToOne') { hasManyPath = buildCollectionName(descriptor.type); } else { - const collectionName = buildCollectionName(modelClass.modelName as string); + const collectionName = buildCollectionName( + modelClass.modelName as string, + ); const docId = resourceHash.id; hasManyPath = `${collectionName}/${docId}/${name}`; @@ -115,7 +130,9 @@ export default class CloudFirestoreSerializer extends JSONSerializer { snapshot: DS.Snapshot, options: Record, ): Record { - const json: { [key: string]: unknown } = { ...super.serialize(snapshot, options) }; + const json: { [key: string]: unknown } = { + ...super.serialize(snapshot, options), + }; snapshot.eachRelationship((name: string, relationship) => { if (relationship.kind === 'hasMany') { diff --git a/addon/services/-firestore-data-manager.ts b/addon/services/-firestore-data-manager.ts index e34cf634..050b18b3 100644 --- a/addon/services/-firestore-data-manager.ts +++ b/addon/services/-firestore-data-manager.ts @@ -13,7 +13,11 @@ import { Unsubscribe, } from 'firebase/firestore'; -import { getDoc, getDocs, onSnapshot } from 'ember-cloud-firestore-adapter/firebase/firestore'; +import { + getDoc, + getDocs, + onSnapshot, +} from 'ember-cloud-firestore-adapter/firebase/firestore'; import flattenDocSnapshot from 'ember-cloud-firestore-adapter/-private/flatten-doc-snapshot'; interface DocListeners { @@ -41,8 +45,8 @@ interface QueryFetchConfig { modelName: keyof ModelRegistry; referenceKeyName: string; recordArray: DS.AdapterPopulatedRecordArray; - queryRef: Query, - queryId?: string, + queryRef: Query; + queryId?: string; } interface HasManyFetchConfig { @@ -68,10 +72,18 @@ export default class FirestoreDataManager extends Service { public willDestroy(): void { super.willDestroy(); - Object.values(this.docListeners).forEach((listener) => listener.unsubscribe()); - Object.values(this.colListeners).forEach((listener) => listener.unsubscribe()); - Object.values(this.queryListeners).forEach((listener) => listener.unsubscribe()); - Object.values(this.hasManyListeners).forEach((listener) => listener.unsubscribe()); + Object.values(this.docListeners).forEach((listener) => + listener.unsubscribe(), + ); + Object.values(this.colListeners).forEach((listener) => + listener.unsubscribe(), + ); + Object.values(this.queryListeners).forEach((listener) => + listener.unsubscribe(), + ); + Object.values(this.hasManyListeners).forEach((listener) => + listener.unsubscribe(), + ); } public async findRecordRealtime( @@ -100,8 +112,11 @@ export default class FirestoreDataManager extends Service { return this.colListeners[listenerKey].snapshot; } - public async queryRealtime(config: QueryFetchConfig): Promise { - const queryId = config.queryId || Math.random().toString(32).slice(2).substring(0, 5); + public async queryRealtime( + config: QueryFetchConfig, + ): Promise { + const queryId = + config.queryId || Math.random().toString(32).slice(2).substring(0, 5); let unsubscribe: Unsubscribe | undefined; if (this.queryListeners[queryId]) { @@ -118,7 +133,9 @@ export default class FirestoreDataManager extends Service { return this.queryListeners[queryId].snapshots; } - public async findHasManyRealtime(config: HasManyFetchConfig): Promise { + public async findHasManyRealtime( + config: HasManyFetchConfig, + ): Promise { const queryId = `${config.modelName}_${config.id}_${config.field}`; let unsubscribe: Unsubscribe | undefined; @@ -142,9 +159,9 @@ export default class FirestoreDataManager extends Service { referenceKey: string, ): Promise { const querySnapshot = await getDocs(queryRef); - const promises = querySnapshot.docs.map((docSnapshot) => ( - this.getReferenceToDoc(docSnapshot, '', referenceKey) - )); + const promises = querySnapshot.docs.map((docSnapshot) => + this.getReferenceToDoc(docSnapshot, '', referenceKey), + ); return Promise.all(promises); } @@ -155,18 +172,32 @@ export default class FirestoreDataManager extends Service { ): Promise { return new Promise((resolve, reject) => { const { path: listenerKey } = docRef; - const unsubscribe = onSnapshot(docRef, (docSnapshot) => { - if (Object.prototype.hasOwnProperty.call(this.docListeners, listenerKey)) { - this.handleSubsequentDocRealtimeUpdates(docSnapshot, modelName, listenerKey); - } else { - this.handleInitialDocRealtimeUpdates(docSnapshot, listenerKey, unsubscribe); - } - - resolve(); - }, (error) => { - this.destroyListener('doc', listenerKey); - reject(error); - }); + const unsubscribe = onSnapshot( + docRef, + (docSnapshot) => { + if ( + Object.prototype.hasOwnProperty.call(this.docListeners, listenerKey) + ) { + this.handleSubsequentDocRealtimeUpdates( + docSnapshot, + modelName, + listenerKey, + ); + } else { + this.handleInitialDocRealtimeUpdates( + docSnapshot, + listenerKey, + unsubscribe, + ); + } + + resolve(); + }, + (error) => { + this.destroyListener('doc', listenerKey); + reject(error); + }, + ); }); } @@ -176,64 +207,98 @@ export default class FirestoreDataManager extends Service { ): Promise { return new Promise((resolve, reject) => { const { path: listenerKey } = colRef; - const unsubscribe = onSnapshot(colRef, (querySnapshot) => { - if (Object.prototype.hasOwnProperty.call(this.colListeners, listenerKey)) { - this.handleSubsequentColRealtimeUpdates(modelName, listenerKey, querySnapshot); - } else { - this.colListeners[listenerKey] = { unsubscribe, snapshot: querySnapshot }; - } - - resolve(); - }, (error) => { - this.destroyListener('col', listenerKey); - reject(error); - }); + const unsubscribe = onSnapshot( + colRef, + (querySnapshot) => { + if ( + Object.prototype.hasOwnProperty.call(this.colListeners, listenerKey) + ) { + this.handleSubsequentColRealtimeUpdates( + modelName, + listenerKey, + querySnapshot, + ); + } else { + this.colListeners[listenerKey] = { + unsubscribe, + snapshot: querySnapshot, + }; + } + + resolve(); + }, + (error) => { + this.destroyListener('col', listenerKey); + reject(error); + }, + ); }); } - public setupQueryRealtimeUpdates(config: QueryFetchConfig, queryId: string): Promise { + public setupQueryRealtimeUpdates( + config: QueryFetchConfig, + queryId: string, + ): Promise { return new Promise((resolve, reject) => { - const unsubscribe = onSnapshot(config.queryRef, (querySnapshot) => { - if (Object.prototype.hasOwnProperty.call(this.queryListeners, queryId)) { - this.handleSubsequentQueryRealtimeUpdates(queryId, config.recordArray); - resolve(); - } else { - this.handleInitialQueryRealtimeUpdates( - queryId, - config, - querySnapshot, - unsubscribe, - ).then(() => { + const unsubscribe = onSnapshot( + config.queryRef, + (querySnapshot) => { + if ( + Object.prototype.hasOwnProperty.call(this.queryListeners, queryId) + ) { + this.handleSubsequentQueryRealtimeUpdates( + queryId, + config.recordArray, + ); resolve(); - }); - } - }, (error) => { - this.destroyListener('query', queryId); - reject(error); - }); + } else { + this.handleInitialQueryRealtimeUpdates( + queryId, + config, + querySnapshot, + unsubscribe, + ).then(() => { + resolve(); + }); + } + }, + (error) => { + this.destroyListener('query', queryId); + reject(error); + }, + ); }); } - public setupHasManyRealtimeUpdates(config: HasManyFetchConfig, queryId: string): Promise { + public setupHasManyRealtimeUpdates( + config: HasManyFetchConfig, + queryId: string, + ): Promise { return new Promise((resolve, reject) => { - const unsubscribe = onSnapshot(config.queryRef, (querySnapshot) => { - if (Object.prototype.hasOwnProperty.call(this.hasManyListeners, queryId)) { - this.handleSubsequentHasManyRealtimeUpdates(config); - resolve(); - } else { - this.handleInitialHasManyRealtimeUpdates( - queryId, - config, - querySnapshot, - unsubscribe, - ).then(() => { + const unsubscribe = onSnapshot( + config.queryRef, + (querySnapshot) => { + if ( + Object.prototype.hasOwnProperty.call(this.hasManyListeners, queryId) + ) { + this.handleSubsequentHasManyRealtimeUpdates(config); resolve(); - }); - } - }, (error) => { - this.destroyListener('hasMany', queryId); - reject(error); - }); + } else { + this.handleInitialHasManyRealtimeUpdates( + queryId, + config, + querySnapshot, + unsubscribe, + ).then(() => { + resolve(); + }); + } + }, + (error) => { + this.destroyListener('hasMany', queryId); + reject(error); + }, + ); }); } @@ -286,9 +351,13 @@ export default class FirestoreDataManager extends Service { querySnapshot: QuerySnapshot, unsubscribe: Unsubscribe, ): Promise { - const docSnapshots = querySnapshot.docs.map((docSnapshot) => ( - this.getReferenceToDoc(docSnapshot, config.modelName, config.referenceKeyName) - )); + const docSnapshots = querySnapshot.docs.map((docSnapshot) => + this.getReferenceToDoc( + docSnapshot, + config.modelName, + config.referenceKeyName, + ), + ); const result = await Promise.all(docSnapshots); @@ -323,23 +392,31 @@ export default class FirestoreDataManager extends Service { querySnapshot: QuerySnapshot, unsubscribe: Unsubscribe, ): Promise { - const docSnapshots = querySnapshot.docs.map((docSnapshot) => ( - this.getReferenceToDoc(docSnapshot, config.modelName, config.referenceKeyName) - )); + const docSnapshots = querySnapshot.docs.map((docSnapshot) => + this.getReferenceToDoc( + docSnapshot, + config.modelName, + config.referenceKeyName, + ), + ); const result = await Promise.all(docSnapshots); this.hasManyListeners[queryId] = { unsubscribe, snapshots: result }; } - private handleSubsequentHasManyRealtimeUpdates(config: HasManyFetchConfig): void { + private handleSubsequentHasManyRealtimeUpdates( + config: HasManyFetchConfig, + ): void { // Schedule for next runloop to avoid race condition errors. This can happen when a listener // exists for a record that's part of the hasMany array. When that happens, doing a reload // in the hasMany array while the record is being unloaded from store can cause an error. // 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); hasManyRef.reload(); }); @@ -354,13 +431,18 @@ export default class FirestoreDataManager extends Service { const referenceTo = docSnapshot.get(referenceKeyName); if (referenceTo && referenceTo.firestore) { - return isRealtime ? this.findRecordRealtime(modelName, referenceTo) : getDoc(referenceTo); + return isRealtime + ? this.findRecordRealtime(modelName, referenceTo) + : getDoc(referenceTo); } return docSnapshot; } - private pushRecord(modelName: keyof ModelRegistry, snapshot: DocumentSnapshot): void { + private pushRecord( + modelName: keyof ModelRegistry, + snapshot: DocumentSnapshot, + ): void { const flatRecord = flattenDocSnapshot(snapshot); const normalizedRecord = this.store.normalize(modelName, flatRecord); @@ -373,7 +455,11 @@ export default class FirestoreDataManager extends Service { } } - private unloadRecord(modelName: keyof ModelRegistry, 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/addon/session-stores/firebase.ts b/addon/session-stores/firebase.ts index 266415d6..0fac5c2c 100644 --- a/addon/session-stores/firebase.ts +++ b/addon/session-stores/firebase.ts @@ -10,7 +10,11 @@ export default class FirebaseStore extends LocalStorageStore { public restore(): Promise { if (this.fastboot?.isFastBoot) { - if (this.fastboot.request.headers.get('Authorization')?.startsWith('Bearer ')) { + if ( + this.fastboot.request.headers + .get('Authorization') + ?.startsWith('Bearer ') + ) { return Promise.resolve({ authenticated: { authenticator: 'authenticator:firebase' }, }); diff --git a/addon/transforms/timestamp.ts b/addon/transforms/timestamp.ts index e73c54a7..56fdb584 100644 --- a/addon/transforms/timestamp.ts +++ b/addon/transforms/timestamp.ts @@ -15,7 +15,7 @@ export default class TimestampTransform extends Transform { } public serialize(value: unknown): Date | FieldValue { - return typeOf(value) === 'date' ? value as Date : serverTimestamp(); + return typeOf(value) === 'date' ? (value as Date) : serverTimestamp(); } } diff --git a/app/instance-initializers/firebase-settings.js b/app/instance-initializers/firebase-settings.js index 955d8209..f3bc8baf 100644 --- a/app/instance-initializers/firebase-settings.js +++ b/app/instance-initializers/firebase-settings.js @@ -1 +1,4 @@ -export { default, initialize } from 'ember-cloud-firestore-adapter/instance-initializers/firebase-settings'; +export { + default, + initialize, +} from 'ember-cloud-firestore-adapter/instance-initializers/firebase-settings'; diff --git a/index.js b/index.js index e58b33d3..bf70e554 100644 --- a/index.js +++ b/index.js @@ -9,16 +9,21 @@ module.exports = { included(app) { this._super.included.apply(this, arguments); - if (Object.prototype.hasOwnProperty.call(app.options, 'esw-ember-cloud-firestore-adapter')) { - this.serviceWorkerOption = app.options['esw-ember-cloud-firestore-adapter']; + if ( + Object.prototype.hasOwnProperty.call( + app.options, + 'esw-ember-cloud-firestore-adapter', + ) + ) { + this.serviceWorkerOption = + app.options['esw-ember-cloud-firestore-adapter']; } else { this.serviceWorkerOption = {}; } - const { - firebaseConfig, - firebaseVersion, - } = this.project.config(app.env)['ember-cloud-firestore-adapter']; + const { firebaseConfig, firebaseVersion } = this.project.config(app.env)[ + 'ember-cloud-firestore-adapter' + ]; this.serviceWorkerOption = Object.assign({}, this.serviceWorkerOption, { firebaseConfig, diff --git a/scripts/build-wrappers.ts b/scripts/build-wrappers.ts index 204a340f..83c7d66d 100644 --- a/scripts/build-wrappers.ts +++ b/scripts/build-wrappers.ts @@ -4,7 +4,11 @@ import * as fs from 'fs'; import { keys } from 'ts-transformer-keys'; -function createFile(outputExports: string[], outputFileName: string, moduleName: string): void { +function createFile( + outputExports: string[], + outputFileName: string, + moduleName: string, +): void { fs.writeFile( `./addon/firebase/${outputFileName}.ts`, `/* eslint-disable max-len */ @@ -37,10 +41,10 @@ async function buildFastBootWrappers( const module = await import(moduleName); const outputExports = Object.keys(module).filter((key) => { if ( - typeof module[key] === 'function' - && moduleExports.includes(key) - && key.charAt(0) === key.charAt(0).toLowerCase() - && !moduleExportsToSkip.includes(key) + typeof module[key] === 'function' && + moduleExports.includes(key) && + key.charAt(0) === key.charAt(0).toLowerCase() && + !moduleExportsToSkip.includes(key) ) { return true; } @@ -53,12 +57,29 @@ async function buildFastBootWrappers( }); } -buildFastBootWrappers('firebase/app', keys(), 'app'); -buildFastBootWrappers('firebase/auth', keys(), 'auth', [ - 'debugErrorMap', - 'inMemoryPersistence', - 'prodErrorMap', -]); -buildFastBootWrappers('firebase/firestore', keys(), 'firestore'); -buildFastBootWrappers('firebase/functions', keys(), 'functions'); -buildFastBootWrappers('firebase/storage', keys(), 'storage'); +buildFastBootWrappers( + 'firebase/app', + keys(), + 'app', +); +buildFastBootWrappers( + 'firebase/auth', + keys(), + 'auth', + ['debugErrorMap', 'inMemoryPersistence', 'prodErrorMap'], +); +buildFastBootWrappers( + 'firebase/firestore', + keys(), + 'firestore', +); +buildFastBootWrappers( + 'firebase/functions', + keys(), + 'functions', +); +buildFastBootWrappers( + 'firebase/storage', + keys(), + 'storage', +); diff --git a/service-worker/index.js b/service-worker/index.js index 20ce12bf..e05d5929 100644 --- a/service-worker/index.js +++ b/service-worker/index.js @@ -6,8 +6,12 @@ import { firebaseConfig, } from 'ember-cloud-firestore-adapter/service-worker/config'; -importScripts(`https://www.gstatic.com/firebasejs/${firebaseVersion}/firebase-app-compat.js`); -importScripts(`https://www.gstatic.com/firebasejs/${firebaseVersion}/firebase-auth-compat.js`); +importScripts( + `https://www.gstatic.com/firebasejs/${firebaseVersion}/firebase-app-compat.js`, +); +importScripts( + `https://www.gstatic.com/firebasejs/${firebaseVersion}/firebase-auth-compat.js`, +); firebase.initializeApp(firebaseConfig); @@ -17,7 +21,10 @@ function getIdToken() { unsubscribe(); if (user) { - user.getIdToken().then((idToken) => resolve(idToken)).catch(() => resolve(null)); + user + .getIdToken() + .then((idToken) => resolve(idToken)) + .catch(() => resolve(null)); } else { resolve(null); } @@ -43,9 +50,10 @@ self.addEventListener('fetch', (event) => { const { origin: eventRequestUrlOrigin } = new URL(event.request.url); if ( - self.location.origin === eventRequestUrlOrigin - && (self.location.protocol === 'https:' || self.location.hostname === 'localhost') - && idToken + self.location.origin === eventRequestUrlOrigin && + (self.location.protocol === 'https:' || + self.location.hostname === 'localhost') && + idToken ) { const headers = cloneHeaderWithIdToken(req.headers, idToken); @@ -71,5 +79,7 @@ self.addEventListener('fetch', (event) => { return fetch(req); }; - event.respondWith(getIdToken().then(requestProcessor).catch(requestProcessor)); + event.respondWith( + getIdToken().then(requestProcessor).catch(requestProcessor), + ); }); diff --git a/tests/acceptance/features-test.ts b/tests/acceptance/features-test.ts index c66a1116..d425c64b 100644 --- a/tests/acceptance/features-test.ts +++ b/tests/acceptance/features-test.ts @@ -4,7 +4,11 @@ import { setupApplicationTest } from 'ember-qunit'; import { Firestore } from 'firebase/firestore'; -import { doc, getDoc, getFirestore } from 'ember-cloud-firestore-adapter/firebase/firestore'; +import { + doc, + getDoc, + getFirestore, +} from 'ember-cloud-firestore-adapter/firebase/firestore'; import resetFixtureData from '../helpers/reset-fixture-data'; module('Acceptance | features', function (hooks) { @@ -57,7 +61,9 @@ module('Acceptance | features', function (hooks) { await visit('/features'); // Act - await click('[data-test-button="create-record-without-belongs-to-relationship"]'); + await click( + '[data-test-button="create-record-without-belongs-to-relationship"]', + ); // Assert await waitFor('[data-test-id]', { timeout: 5000 }); @@ -73,7 +79,9 @@ module('Acceptance | features', function (hooks) { await visit('/features'); // Act - await click('[data-test-button="create-record-with-belongs-to-build-reference"]'); + await click( + '[data-test-button="create-record-with-belongs-to-build-reference"]', + ); // Assert await waitFor('[data-test-id]', { timeout: 5000 }); @@ -83,7 +91,10 @@ module('Acceptance | features', function (hooks) { const createdRecord = await getDoc(doc(db, 'posts/new_post')); - assert.strictEqual(createdRecord.get('publisher').path, 'publishers/user_a'); + assert.strictEqual( + createdRecord.get('publisher').path, + 'publishers/user_a', + ); }); test('should update record', async function (assert) { diff --git a/tests/dummy/app/adapters/application.ts b/tests/dummy/app/adapters/application.ts index 4c7732da..fb6c060f 100644 --- a/tests/dummy/app/adapters/application.ts +++ b/tests/dummy/app/adapters/application.ts @@ -1,10 +1,10 @@ import CloudFirestoreAdapter from 'ember-cloud-firestore-adapter/adapters/cloud-firestore-modular'; -export default class ApplicationAdapter extends CloudFirestoreAdapter { } +export default class ApplicationAdapter extends CloudFirestoreAdapter {} // DO NOT DELETE: this is how TypeScript knows how to look up your adapters. declare module 'ember-data/types/registries/adapter' { export default interface AdapterRegistry { - 'application': ApplicationAdapter; + application: ApplicationAdapter; } } diff --git a/tests/dummy/app/controllers/application.ts b/tests/dummy/app/controllers/application.ts index fd6a30c2..5e538869 100644 --- a/tests/dummy/app/controllers/application.ts +++ b/tests/dummy/app/controllers/application.ts @@ -12,17 +12,22 @@ import { export default class ApplicationController extends Controller { @service - declare public session: SessionService; + public declare session: SessionService; - public updateRecordParam: string = Math.random().toString(32).slice(2).substring(0, 5); + public updateRecordParam: string = Math.random() + .toString(32) + .slice(2) + .substring(0, 5); @action login(): void { - this.session.authenticate('authenticator:firebase', (auth: Auth) => ( + this.session.authenticate('authenticator:firebase', (auth: Auth) => createUserWithEmailAndPassword(auth, 'foo@gmail.com', 'foobar') - ).then((credential) => credential.user).catch(() => ( - signInWithEmailAndPassword(auth, 'foo@gmail.com', 'foobar') - ))); + .then((credential) => credential.user) + .catch(() => + signInWithEmailAndPassword(auth, 'foo@gmail.com', 'foobar'), + ), + ); } @action diff --git a/tests/dummy/app/controllers/features.ts b/tests/dummy/app/controllers/features.ts index 2ceddc08..93588e63 100644 --- a/tests/dummy/app/controllers/features.ts +++ b/tests/dummy/app/controllers/features.ts @@ -7,7 +7,11 @@ import Store from '@ember-data/store'; import { CollectionReference, Firestore } from 'firebase/firestore'; -import { collection, query, where } from 'ember-cloud-firestore-adapter/firebase/firestore'; +import { + collection, + query, + where, +} from 'ember-cloud-firestore-adapter/firebase/firestore'; import UserModel from '../models/user'; export default class FeaturesController extends Controller { @@ -19,21 +23,25 @@ export default class FeaturesController extends Controller { @action public async handleCreateRecordWithIdClick(): Promise { - const user = await this.store.createRecord('user', { - id: 'new', - name: 'new_user_created_with_id', - age: 25, - }).save(); + const user = await this.store + .createRecord('user', { + id: 'new', + name: 'new_user_created_with_id', + age: 25, + }) + .save(); this.users = [user]; } @action public async handleCreateRecordWithoutIdClick(): Promise { - const user = await this.store.createRecord('user', { - name: 'new_user_created_without_id', - age: 30, - }).save(); + const user = await this.store + .createRecord('user', { + name: 'new_user_created_without_id', + age: 30, + }) + .save(); this.users = [user]; } @@ -41,11 +49,13 @@ export default class FeaturesController extends Controller { @action public async handleCreateRecordWithoutBelongsToRelationship(): Promise { const user = await this.store.findRecord('user', 'user_a'); - const post = await this.store.createRecord('post', { - // No belongs to relationship for Group model - author: user, - title: 'What does having it all mean to you? (By: Gabe Lewis)', - }).save(); + const post = await this.store + .createRecord('post', { + // No belongs to relationship for Group model + author: user, + title: 'What does having it all mean to you? (By: Gabe Lewis)', + }) + .save(); const author = await post.get('author'); this.users = [author]; @@ -54,11 +64,13 @@ export default class FeaturesController extends Controller { @action public async handleCreateRecordWithBelongsToBuildReference(): Promise { const user = await this.store.findRecord('user', 'user_a'); - const post = await this.store.createRecord('post', { - id: 'new_post', - publisher: user, - title: 'What does having it all mean to you? (By: Gabe Lewis)', - }).save(); + const post = await this.store + .createRecord('post', { + id: 'new_post', + publisher: user, + title: 'What does having it all mean to you? (By: Gabe Lewis)', + }) + .save(); const publisher = await post.get('publisher'); this.users = [publisher]; diff --git a/tests/dummy/app/controllers/query.ts b/tests/dummy/app/controllers/query.ts index 39ddf7a1..ae4eae93 100644 --- a/tests/dummy/app/controllers/query.ts +++ b/tests/dummy/app/controllers/query.ts @@ -15,9 +15,8 @@ export default class QueryController extends Controller { @action public async handleLoadMoreClick(): Promise { - this.model.set( - 'query.filter', - (reference: CollectionReference) => query(reference, orderBy('name'), limit(5)), + this.model.set('query.filter', (reference: CollectionReference) => + query(reference, orderBy('name'), limit(5)), ); await this.model.update(); diff --git a/tests/dummy/app/models/group.ts b/tests/dummy/app/models/group.ts index 315a2398..7addd324 100644 --- a/tests/dummy/app/models/group.ts +++ b/tests/dummy/app/models/group.ts @@ -14,10 +14,10 @@ import UserModel from './user'; export default class GroupModel extends Model { @attr('string') - declare public name: string; + public declare name: string; @hasMany('user', { async: true, inverse: 'groups' }) - declare public members: DS.PromiseManyArray; + public declare members: DS.PromiseManyArray; @hasMany('post', { async: true, @@ -28,12 +28,12 @@ export default class GroupModel extends Model { return query(reference, limit(1)); }, }) - declare public posts: DS.PromiseManyArray; + public declare posts: DS.PromiseManyArray; } // DO NOT DELETE: this is how TypeScript knows how to look up your models. declare module 'ember-data/types/registries/model' { export default interface ModelRegistry { - 'group': GroupModel; + group: GroupModel; } } diff --git a/tests/dummy/app/models/post.ts b/tests/dummy/app/models/post.ts index 392f4ea9..6357f70c 100644 --- a/tests/dummy/app/models/post.ts +++ b/tests/dummy/app/models/post.ts @@ -15,16 +15,16 @@ import UserModel from './user'; export default class PostModel extends Model { @attr('string') - declare public title: string; + public declare title: string; @attr('timestamp') - declare public createdOn: TimestampTransform; + public declare createdOn: TimestampTransform; @belongsTo('user', { async: true, inverse: 'posts' }) - declare public author: DS.PromiseObject; + public declare author: DS.PromiseObject; @belongsTo('group', { async: true, inverse: 'posts' }) - declare public group: DS.PromiseObject; + public declare group: DS.PromiseObject; @belongsTo('user', { async: true, @@ -34,12 +34,12 @@ export default class PostModel extends Model { return collection(db, 'publishers'); }, }) - declare public publisher: DS.PromiseObject; + public declare publisher: DS.PromiseObject; } // DO NOT DELETE: this is how TypeScript knows how to look up your models. declare module 'ember-data/types/registries/model' { export default interface ModelRegistry { - 'post': PostModel; + post: PostModel; } } diff --git a/tests/dummy/app/models/user.ts b/tests/dummy/app/models/user.ts index 9c8c349f..f644532f 100644 --- a/tests/dummy/app/models/user.ts +++ b/tests/dummy/app/models/user.ts @@ -11,18 +11,18 @@ import PostModel from './post'; export default class UserModel extends Model { @attr('string') - declare public name: string; + public declare name: string; @hasMany('group', { async: true, inverse: 'members' }) - declare public groups: DS.PromiseManyArray; + public declare groups: DS.PromiseManyArray; @hasMany('post', { async: true, inverse: 'author' }) - declare public posts: DS.PromiseManyArray; + public declare posts: DS.PromiseManyArray; } // DO NOT DELETE: this is how TypeScript knows how to look up your models. declare module 'ember-data/types/registries/model' { export default interface ModelRegistry { - 'user': UserModel; + user: UserModel; } } diff --git a/tests/dummy/app/routes/create-record.ts b/tests/dummy/app/routes/create-record.ts index 05802155..f4e78bbd 100644 --- a/tests/dummy/app/routes/create-record.ts +++ b/tests/dummy/app/routes/create-record.ts @@ -12,10 +12,12 @@ export default class CreateRecordRoute extends Route { const group = await this.store.findRecord('group', 'group_a'); const author = await this.store.findRecord('user', 'user_a'); - return this.store.createRecord('post', { - author, - group, - title: 'What does having it all mean to you? (By: Gabe Lewis)', - }).save(); + return this.store + .createRecord('post', { + author, + group, + title: 'What does having it all mean to you? (By: Gabe Lewis)', + }) + .save(); } } diff --git a/tests/dummy/app/routes/features.ts b/tests/dummy/app/routes/features.ts index b287c8cd..42997c87 100644 --- a/tests/dummy/app/routes/features.ts +++ b/tests/dummy/app/routes/features.ts @@ -1,3 +1,3 @@ import Route from '@ember/routing/route'; -export default class FeaturesRoute extends Route { } +export default class FeaturesRoute extends Route {} diff --git a/tests/dummy/app/serializers/application.ts b/tests/dummy/app/serializers/application.ts index 84cce071..656530c0 100644 --- a/tests/dummy/app/serializers/application.ts +++ b/tests/dummy/app/serializers/application.ts @@ -1,10 +1,10 @@ import CloudFirestoreSerializer from 'ember-cloud-firestore-adapter/serializers/cloud-firestore-modular'; -export default class ApplicationSerializer extends CloudFirestoreSerializer { } +export default class ApplicationSerializer extends CloudFirestoreSerializer {} // DO NOT DELETE: this is how TypeScript knows how to look up your serializers. declare module 'ember-data/types/registries/serializer' { export default interface SerializerRegistry { - 'application': ApplicationSerializer; + application: ApplicationSerializer; } } diff --git a/tests/dummy/config/environment.js b/tests/dummy/config/environment.js index 502bb39d..c1ecd033 100644 --- a/tests/dummy/config/environment.js +++ b/tests/dummy/config/environment.js @@ -26,8 +26,10 @@ module.exports = function (environment) { 'ember-cloud-firestore-adapter': { firebaseConfig: { apiKey: '123qweasd', - authDomain: 'ember-cloud-firestore-adapter-test-project.firebaseapp.com', - databaseURL: 'https://ember-cloud-firestore-adapter-test-project.firebaseio.com', + authDomain: + 'ember-cloud-firestore-adapter-test-project.firebaseapp.com', + databaseURL: + 'https://ember-cloud-firestore-adapter-test-project.firebaseio.com', projectId: 'ember-cloud-firestore-adapter-test-project', storageBucket: 'ember-cloud-firestore-adapter-test-project.appspot.com', messagingSenderId: '123qweasd', diff --git a/tests/helpers/reset-fixture-data.ts b/tests/helpers/reset-fixture-data.ts index 31b39d60..96c88bad 100644 --- a/tests/helpers/reset-fixture-data.ts +++ b/tests/helpers/reset-fixture-data.ts @@ -1,11 +1,17 @@ import { Firestore } from 'firebase/firestore'; -import { doc, writeBatch } from 'ember-cloud-firestore-adapter/firebase/firestore'; +import { + doc, + writeBatch, +} from 'ember-cloud-firestore-adapter/firebase/firestore'; export default async function resetFixtureData(db: Firestore): Promise { - await fetch('http://localhost:8080/emulator/v1/projects/ember-cloud-firestore-adapter-test-project/databases/(default)/documents', { - method: 'DELETE', - }); + await fetch( + 'http://localhost:8080/emulator/v1/projects/ember-cloud-firestore-adapter-test-project/databases/(default)/documents', + { + method: 'DELETE', + }, + ); const batch = writeBatch(db); const testData = { diff --git a/tests/unit/-private/flatten-doc-snapshot-test.ts b/tests/unit/-private/flatten-doc-snapshot-test.ts index 296de747..f94f9902 100644 --- a/tests/unit/-private/flatten-doc-snapshot-test.ts +++ b/tests/unit/-private/flatten-doc-snapshot-test.ts @@ -1,7 +1,11 @@ import { module, test } from 'qunit'; import { setupTest } from 'ember-qunit'; -import { doc, getDoc, getFirestore } from 'ember-cloud-firestore-adapter/firebase/firestore'; +import { + doc, + getDoc, + getFirestore, +} from 'ember-cloud-firestore-adapter/firebase/firestore'; import flattenDocSnapshot from 'ember-cloud-firestore-adapter/-private/flatten-doc-snapshot'; module('Unit | -Private | flatten-doc-snapshot-data', function (hooks) { diff --git a/tests/unit/adapters/cloud-firestore-modular-test.ts b/tests/unit/adapters/cloud-firestore-modular-test.ts index 1a4e1b47..eea4be54 100644 --- a/tests/unit/adapters/cloud-firestore-modular-test.ts +++ b/tests/unit/adapters/cloud-firestore-modular-test.ts @@ -56,14 +56,18 @@ module('Unit | Adapter | cloud firestore modular', function (hooks) { const snapshot = { id: 'user_100', age: 30, username: 'user_100' }; const adapter = this.owner.lookup('adapter:cloud-firestore-modular'); - const updateRecordStub = sinon.stub(adapter, 'updateRecord').returns('foo'); + const updateRecordStub = sinon + .stub(adapter, 'updateRecord') + .returns('foo'); // Act const result = await adapter.createRecord(store, modelClass, snapshot); // Assert assert.strictEqual(result, 'foo'); - assert.ok(updateRecordStub.calledWithExactly(store, modelClass, snapshot)); + assert.ok( + updateRecordStub.calledWithExactly(store, modelClass, snapshot), + ); }); }); @@ -149,7 +153,11 @@ module('Unit | Adapter | cloud firestore modular', function (hooks) { const userA = await getDoc(doc(db, 'users/user_a')); - assert.deepEqual(userA.data(), { age: 50, name: 'user_a', username: 'user_a' }); + assert.deepEqual(userA.data(), { + age: 50, + name: 'user_a', + username: 'user_a', + }); const user100 = await getDoc(doc(db, 'users/user_100')); @@ -276,7 +284,12 @@ module('Unit | Adapter | cloud firestore modular', function (hooks) { const adapter = this.owner.lookup('adapter:cloud-firestore-modular'); // Act - const result = await adapter.findRecord(store, modelClass, modelId, snapshot); + const result = await adapter.findRecord( + store, + modelClass, + modelId, + snapshot, + ); // Assert assert.deepEqual(result, { @@ -304,7 +317,12 @@ module('Unit | Adapter | cloud firestore modular', function (hooks) { const adapter = this.owner.lookup('adapter:cloud-firestore-modular'); // Act - const result = await adapter.findRecord(store, modelClass, modelId, snapshot); + const result = await adapter.findRecord( + store, + modelClass, + modelId, + snapshot, + ); // Assert assert.deepEqual(result, { id: 'user_a', since: 2010 }); @@ -328,7 +346,10 @@ module('Unit | Adapter | cloud firestore modular', function (hooks) { } catch (error) { // Assert assert.ok(error instanceof AdapterRecordNotFoundError); - assert.strictEqual(error.message, 'Record user_100 for model type user doesn\'t exist'); + assert.strictEqual( + error.message, + "Record user_100 for model type user doesn't exist", + ); } }); }); @@ -345,7 +366,12 @@ module('Unit | Adapter | cloud firestore modular', function (hooks) { const adapter = this.owner.lookup('adapter:cloud-firestore-modular'); // Act - const result = await adapter.findBelongsTo(store, snapshot, url, relationship); + const result = await adapter.findBelongsTo( + store, + snapshot, + url, + relationship, + ); // Assert assert.deepEqual(result, { @@ -362,14 +388,17 @@ module('Unit | Adapter | cloud firestore modular', function (hooks) { // Arrange const determineRelationshipTypeStub = sinon.stub().returns('manyToOne'); const inverseForStub = sinon.stub().returns({ name: 'author' }); - this.owner.register('service:store', class extends Store { - modelFor() { - return { - determineRelationshipType: determineRelationshipTypeStub, - inverseFor: inverseForStub, - }; - } - }); + this.owner.register( + 'service:store', + class extends Store { + modelFor() { + return { + determineRelationshipType: determineRelationshipTypeStub, + inverseFor: inverseForStub, + }; + } + }, + ); const store = this.owner.lookup('service:store'); store.normalize = sinon.stub(); @@ -392,25 +421,35 @@ module('Unit | Adapter | cloud firestore modular', function (hooks) { const adapter = this.owner.lookup('adapter:cloud-firestore-modular'); // Act - const result = await adapter.findHasMany(store, snapshot, url, relationship); + const result = await adapter.findHasMany( + store, + snapshot, + url, + relationship, + ); // Assert assert.strictEqual(result[0].id, 'post_a'); assert.strictEqual(result[0].title, 'post_a'); - assert.ok(determineRelationshipTypeStub.calledWithExactly(relationship, store)); + assert.ok( + determineRelationshipTypeStub.calledWithExactly(relationship, store), + ); assert.ok(inverseForStub.calledWithExactly(relationship.key, store)); }); test('should fetch many-to-whatever cardinality', async function (assert) { // Arrange const determineRelationshipTypeStub = sinon.stub().returns('manyToNone'); - this.owner.register('service:store', class extends Store { - modelFor() { - return { - determineRelationshipType: determineRelationshipTypeStub, - }; - } - }); + this.owner.register( + 'service:store', + class extends Store { + modelFor() { + return { + determineRelationshipType: determineRelationshipTypeStub, + }; + } + }, + ); const store = this.owner.lookup('service:store'); store.normalize = sinon.stub(); @@ -433,7 +472,12 @@ module('Unit | Adapter | cloud firestore modular', function (hooks) { const adapter = this.owner.lookup('adapter:cloud-firestore-modular'); // Act - const result = await adapter.findHasMany(store, snapshot, url, relationship); + const result = await adapter.findHasMany( + store, + snapshot, + url, + relationship, + ); // Assert assert.deepEqual(result, [ @@ -444,21 +488,26 @@ module('Unit | Adapter | cloud firestore modular', function (hooks) { username: 'user_b', }, ]); - assert.ok(determineRelationshipTypeStub.calledWithExactly(relationship, store)); + assert.ok( + determineRelationshipTypeStub.calledWithExactly(relationship, store), + ); }); test('should be able to fetch with filter using a record property', async function (assert) { // Arrange const determineRelationshipTypeStub = sinon.stub().returns('manyToOne'); const inverseForStub = sinon.stub().returns({ name: 'author' }); - this.owner.register('service:store', class extends Store { - modelFor() { - return { - determineRelationshipType: determineRelationshipTypeStub, - inverseFor: inverseForStub, - }; - } - }); + this.owner.register( + 'service:store', + class extends Store { + modelFor() { + return { + determineRelationshipType: determineRelationshipTypeStub, + inverseFor: inverseForStub, + }; + } + }, + ); const store = this.owner.lookup('service:store'); store.normalize = sinon.stub(); @@ -483,12 +532,19 @@ module('Unit | Adapter | cloud firestore modular', function (hooks) { const adapter = this.owner.lookup('adapter:cloud-firestore-modular'); // Act - const result = await adapter.findHasMany(store, snapshot, url, relationship); + const result = await adapter.findHasMany( + store, + snapshot, + url, + relationship, + ); // Assert assert.strictEqual(result.length, 1); assert.strictEqual(result[0].id, 'post_a'); - assert.ok(determineRelationshipTypeStub.calledWithExactly(relationship, store)); + assert.ok( + determineRelationshipTypeStub.calledWithExactly(relationship, store), + ); assert.ok(inverseForStub.calledWithExactly(relationship.key, store)); }); @@ -517,7 +573,12 @@ module('Unit | Adapter | cloud firestore modular', function (hooks) { const adapter = this.owner.lookup('adapter:cloud-firestore-modular'); // Act - const result = await adapter.findHasMany(store, snapshot, url, relationship); + const result = await adapter.findHasMany( + store, + snapshot, + url, + relationship, + ); // Assert assert.strictEqual(result[0].id, 'post_b'); diff --git a/tests/unit/authenticators/firebase-test.ts b/tests/unit/authenticators/firebase-test.ts index 2110d83f..da491e01 100644 --- a/tests/unit/authenticators/firebase-test.ts +++ b/tests/unit/authenticators/firebase-test.ts @@ -4,7 +4,11 @@ import { setupTest } from 'ember-qunit'; import { Auth } from 'firebase/auth'; import { Firestore } from 'firebase/firestore'; -import { getAuth, signInAnonymously, signOut } from 'ember-cloud-firestore-adapter/firebase/auth'; +import { + getAuth, + signInAnonymously, + signOut, +} from 'ember-cloud-firestore-adapter/firebase/auth'; import { getFirestore } from 'ember-cloud-firestore-adapter/firebase/firestore'; import resetFixtureData from '../../helpers/reset-fixture-data'; @@ -34,7 +38,9 @@ module('Unit | Authenticator | firebase', function (hooks) { const authenticator = this.owner.lookup('authenticator:firebase'); // Act - const result = await authenticator.authenticate(() => Promise.resolve({ user: 'foo' })); + const result = await authenticator.authenticate(() => + Promise.resolve({ user: 'foo' }), + ); // Assert assert.deepEqual(result, { user: 'foo' }); diff --git a/tests/unit/serializers/cloud-firestore-modular-test.ts b/tests/unit/serializers/cloud-firestore-modular-test.ts index 3ab4e005..8a4b0290 100644 --- a/tests/unit/serializers/cloud-firestore-modular-test.ts +++ b/tests/unit/serializers/cloud-firestore-modular-test.ts @@ -9,7 +9,9 @@ module('Unit | Serializer | cloud-firestore modular', function (hooks) { module('extractRelationship()', function () { test('should return object containing the type and id of a relationship', function (assert) { // Arrange - const serializer = this.owner.lookup('serializer:cloud-firestore-modular'); + const serializer = this.owner.lookup( + 'serializer:cloud-firestore-modular', + ); // Act const result = serializer.extractRelationship('user', { @@ -23,7 +25,9 @@ module('Unit | Serializer | cloud-firestore modular', function (hooks) { test('should return null when without any relationship hash', function (assert) { // Arrange - const serializer = this.owner.lookup('serializer:cloud-firestore-modular'); + const serializer = this.owner.lookup( + 'serializer:cloud-firestore-modular', + ); // Act const result = serializer.extractRelationship('user', null); @@ -36,7 +40,9 @@ module('Unit | Serializer | cloud-firestore modular', function (hooks) { module('extractRelationships()', function () { test('should return object containing manyToMany and manyToOne links', function (assert) { // Arrange - const serializer = this.owner.lookup('serializer:cloud-firestore-modular') as CloudFirestoreSerializer; + const serializer = this.owner.lookup( + 'serializer:cloud-firestore-modular', + ) as CloudFirestoreSerializer; const store = this.owner.lookup('service:store'); serializer.store = store; // TODO: injected store on serializer is undefined in tests diff --git a/tests/unit/services/-firestore-data-manager-test.ts b/tests/unit/services/-firestore-data-manager-test.ts index 90b5f974..6839a312 100644 --- a/tests/unit/services/-firestore-data-manager-test.ts +++ b/tests/unit/services/-firestore-data-manager-test.ts @@ -40,11 +40,18 @@ module('Unit | Service | -firestore-data-manager', function (hooks) { ) as FirestoreDataManager; // Act - const result = await firestoreDataManager.findRecordRealtime(modelName, docRef); + const result = await firestoreDataManager.findRecordRealtime( + modelName, + docRef, + ); // Assert assert.strictEqual(result.id, 'user_a'); - assert.deepEqual(result.data(), { name: 'user_a', age: 15, username: 'user_a' }); + assert.deepEqual(result.data(), { + name: 'user_a', + age: 15, + username: 'user_a', + }); }); test('should push record to store when doc gets updated', async function (assert) { @@ -97,12 +104,19 @@ module('Unit | Service | -firestore-data-manager', function (hooks) { ) as FirestoreDataManager; // Act - const result = await firestoreDataManager.findAllRealtime(modelName, colRef); + const result = await firestoreDataManager.findAllRealtime( + modelName, + colRef, + ); // Assert assert.strictEqual(result.size, 3); assert.strictEqual(result.docs[0].id, 'user_a'); - assert.deepEqual(result.docs[0].data(), { name: 'user_a', age: 15, username: 'user_a' }); + assert.deepEqual(result.docs[0].data(), { + name: 'user_a', + age: 15, + username: 'user_a', + }); }); test('should push every record to store when collection gets updated', async function (assert) { @@ -157,7 +171,8 @@ module('Unit | Service | -firestore-data-manager', function (hooks) { modelName: 'user', referenceKeyName: 'referenceTo', recordArray: { - update: () => DS.PromiseArray.create({ promise: RSVP.Promise.resolve([]) }), + update: () => + DS.PromiseArray.create({ promise: RSVP.Promise.resolve([]) }), } as unknown as DS.AdapterPopulatedRecordArray, }; const firestoreDataManager = this.owner.lookup( @@ -170,7 +185,11 @@ module('Unit | Service | -firestore-data-manager', function (hooks) { // Assert assert.strictEqual(result.length, 3); assert.strictEqual(result[0].id, 'user_a'); - assert.deepEqual(result[0].data(), { name: 'user_a', age: 15, username: 'user_a' }); + assert.deepEqual(result[0].data(), { + name: 'user_a', + age: 15, + username: 'user_a', + }); }); test('should return fetched records with referenceTo indicators', async function (assert) { @@ -182,7 +201,8 @@ module('Unit | Service | -firestore-data-manager', function (hooks) { modelName: 'user', referenceKeyName: 'referenceTo', recordArray: { - update: () => DS.PromiseArray.create({ promise: RSVP.Promise.resolve([]) }), + update: () => + DS.PromiseArray.create({ promise: RSVP.Promise.resolve([]) }), } as unknown as DS.AdapterPopulatedRecordArray, }; const firestoreDataManager = this.owner.lookup( @@ -209,7 +229,8 @@ module('Unit | Service | -firestore-data-manager', function (hooks) { referenceKeyName: 'referenceTo', queryId: 'test', recordArray: { - update: () => DS.PromiseArray.create({ promise: RSVP.Promise.resolve([]) }), + update: () => + DS.PromiseArray.create({ promise: RSVP.Promise.resolve([]) }), } as unknown as DS.AdapterPopulatedRecordArray, }; const updateSpy = sinon.spy(config.recordArray, 'update'); @@ -286,9 +307,15 @@ module('Unit | Service | -firestore-data-manager', function (hooks) { const store = this.owner.lookup('service:store'); const reloadStub = sinon.stub().returns(Promise.resolve()); - sinon.stub(store, 'peekRecord').withArgs('user', 'user_a').returns({ - hasMany: sinon.stub().withArgs('groups').returns({ reload: reloadStub }), - }); + sinon + .stub(store, 'peekRecord') + .withArgs('user', 'user_a') + .returns({ + hasMany: sinon + .stub() + .withArgs('groups') + .returns({ reload: reloadStub }), + }); const docRef = doc(db, 'users/user_a/groups', 'group_a'); const colRef = collection(db, 'users/user_a/groups'); @@ -325,12 +352,19 @@ module('Unit | Service | -firestore-data-manager', function (hooks) { ) as FirestoreDataManager; // Act - const result = await firestoreDataManager.queryWithReferenceTo(queryRef, referenceKeyName); + const result = await firestoreDataManager.queryWithReferenceTo( + queryRef, + referenceKeyName, + ); // Assert assert.strictEqual(result.length, 3); assert.strictEqual(result[0].id, 'user_a'); - assert.deepEqual(result[0].data(), { name: 'user_a', age: 15, username: 'user_a' }); + assert.deepEqual(result[0].data(), { + name: 'user_a', + age: 15, + username: 'user_a', + }); }); test('should return fetched records with referenceTo indicators', async function (assert) { @@ -343,7 +377,10 @@ module('Unit | Service | -firestore-data-manager', function (hooks) { ) as FirestoreDataManager; // Act - const result = await firestoreDataManager.queryWithReferenceTo(queryRef, referenceKeyName); + const result = await firestoreDataManager.queryWithReferenceTo( + queryRef, + referenceKeyName, + ); // Assert assert.strictEqual(result.length, 1); diff --git a/tests/unit/transforms/timestamp-test.ts b/tests/unit/transforms/timestamp-test.ts index 45c8b908..5594d307 100644 --- a/tests/unit/transforms/timestamp-test.ts +++ b/tests/unit/transforms/timestamp-test.ts @@ -4,7 +4,10 @@ import { setupTest } from 'ember-qunit'; import { Firestore } from 'firebase/firestore'; import { - doc, getDoc, getFirestore, serverTimestamp, + doc, + getDoc, + getFirestore, + serverTimestamp, } from 'ember-cloud-firestore-adapter/firebase/firestore'; import resetFixtureData from 'dummy/tests/helpers/reset-fixture-data'; diff --git a/tests/unit/utils/custom-errors-test.ts b/tests/unit/utils/custom-errors-test.ts index 1f9e451c..e5665c3c 100644 --- a/tests/unit/utils/custom-errors-test.ts +++ b/tests/unit/utils/custom-errors-test.ts @@ -8,7 +8,9 @@ module('Unit | Utility | custom-errors', function () { assert.expect(3); try { - throw new AdapterRecordNotFoundError('Test Error', { cause: 'Test Cause' }); + throw new AdapterRecordNotFoundError('Test Error', { + cause: 'Test Cause', + }); } catch (error) { assert.ok(error instanceof AdapterRecordNotFoundError); assert.strictEqual(error.message, 'Test Error'); diff --git a/types/ember-simple-auth/services/session.d.ts b/types/ember-simple-auth/services/session.d.ts index 8658ba52..d36fdf3a 100644 --- a/types/ember-simple-auth/services/session.d.ts +++ b/types/ember-simple-auth/services/session.d.ts @@ -18,34 +18,34 @@ interface Data { declare module 'ember-simple-auth/services/session' { export default class SessionService extends Service.extend(Evented) { /** - * Triggered whenever the session is successfully authenticated. This happens - * when the session gets authenticated via - * {{#crossLink "SessionService/authenticate:method"}}{{/crossLink}} but also - * when the session is authenticated in another tab or window of the same - * application and the session state gets synchronized across tabs or windows - * via the store (see - * {{#crossLink "BaseStore/sessionDataUpdated:event"}}{{/crossLink}}). - * When using the {{#crossLink "ApplicationRouteMixin"}}{{/crossLink}} this - * event will automatically get handled (see - * {{#crossLink "ApplicationRouteMixin/sessionAuthenticated:method"}}{{/crossLink}}). - * @event authenticationSucceeded - * @public - */ + * Triggered whenever the session is successfully authenticated. This happens + * when the session gets authenticated via + * {{#crossLink "SessionService/authenticate:method"}}{{/crossLink}} but also + * when the session is authenticated in another tab or window of the same + * application and the session state gets synchronized across tabs or windows + * via the store (see + * {{#crossLink "BaseStore/sessionDataUpdated:event"}}{{/crossLink}}). + * When using the {{#crossLink "ApplicationRouteMixin"}}{{/crossLink}} this + * event will automatically get handled (see + * {{#crossLink "ApplicationRouteMixin/sessionAuthenticated:method"}}{{/crossLink}}). + * @event authenticationSucceeded + * @public + */ /** - * Triggered whenever the session is successfully invalidated. This happens - * when the session gets invalidated via - * {{#crossLink "SessionService/invalidate:method"}}{{/crossLink}} but also - * when the session is invalidated in another tab or window of the same - * application and the session state gets synchronized across tabs or windows - * via the store (see - * {{#crossLink "BaseStore/sessionDataUpdated:event"}}{{/crossLink}}). - * When using the {{#crossLink "ApplicationRouteMixin"}}{{/crossLink}} this - * event will automatically get handled (see - * {{#crossLink "ApplicationRouteMixin/sessionInvalidated:method"}}{{/crossLink}}). - * @event invalidationSucceeded - * @public - */ + * Triggered whenever the session is successfully invalidated. This happens + * when the session gets invalidated via + * {{#crossLink "SessionService/invalidate:method"}}{{/crossLink}} but also + * when the session is invalidated in another tab or window of the same + * application and the session state gets synchronized across tabs or windows + * via the store (see + * {{#crossLink "BaseStore/sessionDataUpdated:event"}}{{/crossLink}}). + * When using the {{#crossLink "ApplicationRouteMixin"}}{{/crossLink}} this + * event will automatically get handled (see + * {{#crossLink "ApplicationRouteMixin/sessionInvalidated:method"}}{{/crossLink}}). + * @event invalidationSucceeded + * @public + */ isAuthenticated: boolean;