strata-storage
Version:
Zero-dependency universal storage plugin providing a unified API for all storage operations across web, Android, and iOS platforms
230 lines (229 loc) • 9.83 kB
JavaScript
import { StorageError } from "./utils/errors.js";
/**
* Enable Firebase sync for Strata Storage
* This dynamically imports Firebase SDK only when needed
*/
export async function enableFirebaseSync(storage, config) {
// Dynamically import Firebase only when this function is called
try {
// @ts-expect-error - Firebase is an optional peer dependency
const { initializeApp, getApps } = await import('firebase/app');
// Initialize Firebase if not already initialized
if (!getApps().length) {
initializeApp({
apiKey: config.apiKey,
authDomain: config.authDomain,
projectId: config.projectId,
storageBucket: config.storageBucket,
messagingSenderId: config.messagingSenderId,
appId: config.appId,
});
}
if (config.firestore) {
const { getFirestore, doc, setDoc, getDoc, deleteDoc, collection, getDocs, onSnapshot } =
// @ts-expect-error - Firebase is an optional peer dependency
await import('firebase/firestore');
const db = getFirestore();
const collectionName = config.collectionName || 'strata-storage';
// Create custom adapter for Firestore
const firestoreAdapter = {
name: 'firestore',
capabilities: {
persistent: true,
synchronous: false,
observable: true,
transactional: false,
queryable: true,
maxSize: -1,
binary: true,
encrypted: false,
crossTab: true,
},
async get(key) {
const docRef = doc(db, collectionName, key);
const docSnap = await getDoc(docRef);
return docSnap.exists() ? docSnap.data() : null;
},
async set(key, value) {
const docRef = doc(db, collectionName, key);
await setDoc(docRef, { value, timestamp: Date.now() });
},
async remove(key) {
const docRef = doc(db, collectionName, key);
await deleteDoc(docRef);
},
async has(key) {
const docRef = doc(db, collectionName, key);
const docSnap = await getDoc(docRef);
return docSnap.exists();
},
async clear() {
try {
const collectionRef = collection(db, collectionName);
const snapshot = await getDocs(collectionRef);
const deletePromises = snapshot.docs.map((docSnapshot) => deleteDoc(doc(db, collectionName, docSnapshot.id)));
await Promise.all(deletePromises);
}
catch (error) {
throw new StorageError('Failed to clear Firestore collection', {
collectionName,
originalError: error,
});
}
},
async keys() {
try {
const collectionRef = collection(db, collectionName);
const snapshot = await getDocs(collectionRef);
return snapshot.docs.map((docSnapshot) => docSnapshot.id);
}
catch (error) {
throw new StorageError('Failed to retrieve keys from Firestore', {
collectionName,
originalError: error,
});
}
},
async size() {
return { total: 0, count: 0 };
},
async initialize() {
// Already initialized
},
async isAvailable() {
return true;
},
subscribe(callback) {
const collectionRef = collection(db, collectionName);
const unsubscribe = onSnapshot(collectionRef, (snapshot) => {
snapshot
.docChanges()
.forEach((change) => {
const docData = change.doc.data();
callback({
key: change.doc.id,
oldValue: change.type === 'removed' ? docData.value : undefined,
newValue: change.type !== 'removed' ? docData.value : undefined,
source: 'remote',
storage: 'firestore',
timestamp: Date.now(),
});
});
});
return unsubscribe;
},
async close() {
// No cleanup needed
},
};
// Register the Firestore adapter
// Cast to unknown first to bypass type checking for custom adapter
storage.registerAdapter(firestoreAdapter);
}
if (config.realtimeDatabase) {
// @ts-expect-error - Firebase is an optional peer dependency
const { getDatabase, ref, set, get, remove, onValue } = await import('firebase/database');
const db = getDatabase();
// Create custom adapter for Realtime Database
const realtimeAdapter = {
name: 'realtime',
capabilities: {
persistent: true,
synchronous: false,
observable: true,
transactional: false,
queryable: false,
maxSize: -1,
binary: false,
encrypted: false,
crossTab: true,
},
async get(key) {
const snapshot = await get(ref(db, `strata-storage/${key}`));
return snapshot.exists() ? snapshot.val() : null;
},
async set(key, value) {
await set(ref(db, `strata-storage/${key}`), value);
},
async remove(key) {
await remove(ref(db, `strata-storage/${key}`));
},
async has(key) {
const snapshot = await get(ref(db, `strata-storage/${key}`));
return snapshot.exists();
},
async clear() {
await remove(ref(db, 'strata-storage'));
},
async keys() {
const snapshot = await get(ref(db, 'strata-storage'));
return snapshot.exists() ? Object.keys(snapshot.val()) : [];
},
async size() {
const snapshot = await get(ref(db, 'strata-storage'));
const count = snapshot.exists() ? Object.keys(snapshot.val()).length : 0;
return { total: count * 100, count }; // Rough estimate
},
async initialize() {
// Already initialized
},
async isAvailable() {
return true;
},
subscribe(callback) {
const dbRef = ref(db, 'strata-storage');
let previousData = {};
const unsubscribe = onValue(dbRef, (snapshot) => {
const snapshotTyped = snapshot;
const currentData = snapshotTyped.exists()
? snapshotTyped.val()
: {};
const allKeys = new Set([...Object.keys(previousData), ...Object.keys(currentData)]);
allKeys.forEach((key) => {
const oldValue = previousData[key];
const newValue = currentData[key];
if (oldValue !== newValue) {
callback({
key,
oldValue,
newValue,
source: 'remote',
storage: 'realtime',
timestamp: Date.now(),
});
}
});
previousData = { ...currentData };
});
return unsubscribe;
},
async close() {
// No cleanup needed
},
};
// Register the Realtime Database adapter
// Cast to unknown first to bypass type checking for custom adapter
storage.registerAdapter(realtimeAdapter);
}
// Firebase sync enabled successfully
}
catch (error) {
throw new StorageError('Failed to enable Firebase sync', {
originalError: error,
config,
});
}
}
/**
* Check if Firebase is available in the environment
*/
export async function isFirebaseAvailable() {
try {
// @ts-expect-error - Firebase is an optional peer dependency
await import('firebase/app');
return true;
}
catch {
return false;
}
}