Skip to content

Commit

Permalink
enforce the type checking by replacing the plugin proxy by a class
Browse files Browse the repository at this point in the history
  • Loading branch information
mamillastre committed Jan 9, 2025
1 parent 38959fd commit fc98ced
Show file tree
Hide file tree
Showing 2 changed files with 127 additions and 74 deletions.
197 changes: 125 additions & 72 deletions packages/firestore/src/client.ts
Original file line number Diff line number Diff line change
@@ -1,76 +1,125 @@
import { Capacitor } from '@capacitor/core';
import type { DocumentData } from 'firebase/firestore';
import { Timestamp as OriginalTimestamp } from 'firebase/firestore';

import type {
DocumentSnapshot,
AddCollectionGroupSnapshotListenerCallback,
AddCollectionGroupSnapshotListenerOptions,
AddCollectionSnapshotListenerCallback,
AddCollectionSnapshotListenerOptions,
AddDocumentOptions,
AddDocumentResult,
AddDocumentSnapshotListenerCallback,
AddDocumentSnapshotListenerOptions,
CallbackId,
DeleteDocumentOptions,
DocumentData,
FirebaseFirestorePlugin,
GetCollectionGroupOptions,
GetCollectionGroupResult,
GetCollectionOptions,
GetCollectionResult,
GetDocumentOptions,
GetDocumentResult,
RemoveSnapshotListenerOptions,
SetDocumentOptions,
UpdateDocumentOptions,
UseEmulatorOptions,
WriteBatchOptions,
} from './definitions';
import type { CustomField, CustomTimestamp } from './internals';
import { FIRESTORE_FIELD_TYPE, FIRESTORE_FIELD_VALUE } from './internals';
import { Timestamp } from './timestamp';

/**
* Apply a proxy on the plugin to manage document data parsing
* @param plugin The capacitor plugin
* @returns A proxied plugin that manage parsing
* The plugin client that manage all the data parsing / format between the web and the native platform
*/
export function getClientPlugin(
plugin: FirebaseFirestorePlugin,
): FirebaseFirestorePlugin {
return new Proxy(plugin, {
get(target, prop, receiver) {
const getProperty = Reflect.get(target, prop, receiver);
export class FirebaseFirestoreClient implements FirebaseFirestorePlugin {
private readonly plugin: FirebaseFirestorePlugin;

// Get document, collection or collection group
if (
prop === 'getDocument' ||
prop === 'getCollection' ||
prop === 'getCollectionGroup'
) {
return async function (options: any): Promise<any> {
return parseResult(await getProperty(options));
};
}

// Add, update, set document
if (
prop === 'addDocument' ||
prop === 'setDocument' ||
prop === 'updateDocument'
) {
return async function (options: any): Promise<any> {
return getProperty(formatOptionsData(options));
};
}

// Write batch
if (prop === 'writeBatch') {
return async function (options: WriteBatchOptions): Promise<void> {
options.operations = options.operations.map(operation =>
formatOptionsData(operation),
);
return getProperty(options);
};
}

// Listener document, collection, collection group
if (
prop === 'addDocumentSnapshotListener' ||
prop === 'addCollectionSnapshotListener' ||
prop === 'addCollectionGroupSnapshotListener'
) {
return async function (options: any, callback: any): Promise<any> {
return getProperty(options, (ev: any, err: any) =>
callback(ev ? parseResult(ev as any) : ev, err),
);
};
}
constructor(plugin: FirebaseFirestorePlugin) {
this.plugin = plugin;
}

return getProperty;
},
});
addDocument(options: AddDocumentOptions): Promise<AddDocumentResult> {
return this.plugin.addDocument(formatOptionsData(options));
}
setDocument(options: SetDocumentOptions): Promise<void> {
return this.plugin.setDocument(formatOptionsData(options));
}
async getDocument<T extends DocumentData = DocumentData>(
options: GetDocumentOptions,
): Promise<GetDocumentResult<T>> {
return parseResult(await this.plugin.getDocument<T>(options));
}
updateDocument(options: UpdateDocumentOptions): Promise<void> {
return this.plugin.updateDocument(formatOptionsData(options));
}
deleteDocument(options: DeleteDocumentOptions): Promise<void> {
return this.plugin.deleteDocument(options);
}
writeBatch(options: WriteBatchOptions): Promise<void> {
return this.plugin.writeBatch({
...options,
operations: options.operations.map(operation =>
formatOptionsData(operation),
),
});
}
async getCollection<T extends DocumentData = DocumentData>(
options: GetCollectionOptions,
): Promise<GetCollectionResult<T>> {
return parseResult(await this.plugin.getCollection<T>(options));
}
async getCollectionGroup<T extends DocumentData = DocumentData>(
options: GetCollectionGroupOptions,
): Promise<GetCollectionGroupResult<T>> {
return parseResult(await this.plugin.getCollectionGroup<T>(options));
}
clearPersistence(): Promise<void> {
return this.plugin.clearPersistence();
}
enableNetwork(): Promise<void> {
return this.plugin.enableNetwork();
}
disableNetwork(): Promise<void> {
return this.plugin.disableNetwork();
}
useEmulator(options: UseEmulatorOptions): Promise<void> {
return this.plugin.useEmulator(options);
}
addDocumentSnapshotListener<T extends DocumentData = DocumentData>(
options: AddDocumentSnapshotListenerOptions,
callback: AddDocumentSnapshotListenerCallback<T>,
): Promise<CallbackId> {
return this.plugin.addDocumentSnapshotListener<T>(options, (ev, err) =>
callback(parseResult(ev), err),
);
}
addCollectionSnapshotListener<T extends DocumentData = DocumentData>(
options: AddCollectionSnapshotListenerOptions,
callback: AddCollectionSnapshotListenerCallback<T>,
): Promise<CallbackId> {
return this.plugin.addCollectionSnapshotListener<T>(options, (ev, err) =>
callback(parseResult(ev), err),
);
}
addCollectionGroupSnapshotListener<T extends DocumentData = DocumentData>(
options: AddCollectionGroupSnapshotListenerOptions,
callback: AddCollectionGroupSnapshotListenerCallback<T>,
): Promise<CallbackId> {
return this.plugin.addCollectionGroupSnapshotListener<T>(
options,
(ev, err) => callback(parseResult(ev), err),
);
}
removeSnapshotListener(
options: RemoveSnapshotListenerOptions,
): Promise<void> {
return this.plugin.removeSnapshotListener(options);
}
removeAllListeners(): Promise<void> {
return this.plugin.removeAllListeners();
}
}

/**
Expand All @@ -88,23 +137,27 @@ function formatOptionsData<T extends { data?: DocumentData }>(options: T): T {

/**
* Parse a received result
* @param res The result of a read method
* @param result The result of a read method
* @returns The parsed result
*/
function parseResult<
T,
U extends {
snapshot?: DocumentSnapshot<T>;
snapshots?: DocumentSnapshot<T>[];
},
>(res: U): U {
if (res?.snapshot?.data) {
res.snapshot.data = parseResultDocumentData(res.snapshot.data);
}
if (res?.snapshots) {
res.snapshots.map(s => parseResultDocumentData(s));
}
return res;
T extends DocumentData,
U extends
| Partial<GetDocumentResult<T>>
| Partial<GetCollectionGroupResult<T>>
| null,
>(result: U): U {
if ((result as GetDocumentResult<T>)?.snapshot?.data) {
(result as GetDocumentResult<T>).snapshot.data = parseResultDocumentData(
(result as GetDocumentResult<T>).snapshot.data,
);
}
if ((result as GetCollectionGroupResult<T>)?.snapshots) {
(result as GetCollectionGroupResult<T>).snapshots.map(s =>
parseResultDocumentData(s),
);
}
return result;
}

/**
Expand Down
4 changes: 2 additions & 2 deletions packages/firestore/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import { registerPlugin } from '@capacitor/core';

import { getClientPlugin } from './client';
import { FirebaseFirestoreClient } from './client';
import type { FirebaseFirestorePlugin } from './definitions';

const FirebaseFirestore = getClientPlugin(
const FirebaseFirestore: FirebaseFirestorePlugin = new FirebaseFirestoreClient(
registerPlugin<FirebaseFirestorePlugin>('FirebaseFirestore', {
web: () => import('./web').then(m => new m.FirebaseFirestoreWeb()),
}),
Expand Down

0 comments on commit fc98ced

Please sign in to comment.