XO wallet integration

This commit is contained in:
Jim Hamill
2025-05-05 03:19:47 +00:00
parent f9fe792542
commit e0a554e897
14 changed files with 745 additions and 213 deletions
+54 -52
View File
@@ -1,4 +1,5 @@
import { BaseStore } from './base-store.js';
import { StoreInMemory } from './store-inmemory.js';
/**
* A store that accepts multiple stores and syncs them by key.
@@ -12,127 +13,128 @@ import { BaseStore } from './base-store.js';
*/
export class StoreComposite extends BaseStore {
static async from(
localStore: BaseStore,
remoteStores: Array<BaseStore> = [],
stores: Array<BaseStore> = [],
primaryStore: BaseStore = StoreInMemory.from(),
) {
const compositeStore = new this(localStore, remoteStores);
const compositeStore = new this(stores, primaryStore);
await compositeStore.sync();
return compositeStore;
}
constructor(
private localStore: BaseStore,
private remoteStores: Array<BaseStore> = [],
private stores: Array<BaseStore> = [],
private primaryStore: BaseStore,
) {
super();
}
async all<T>() {
return await this.localStore.all<T>();
return await this.primaryStore.all<T>();
}
async keys() {
return await this.localStore.keys();
return await this.primaryStore.keys();
}
async get<T>(key: string) {
return await this.localStore.get<T>(key);
return await this.primaryStore.get<T>(key);
}
async set<T>(key: string, value: T) {
await this.localStore.set(key, value);
await this.primaryStore.set(key, value);
this.emit('set', { key, value });
}
async delete(key: string) {
await this.localStore.delete(key);
await this.primaryStore.delete(key);
this.emit('delete', { key });
}
async clear() {
await this.localStore.clear();
await this.primaryStore.clear();
this.emit('clear', undefined);
}
async has(key: string) {
return await this.localStore.has(key);
return await this.primaryStore.has(key);
}
async close() {
await Promise.all([
this.localStore.close(),
...this.remoteStores.map((store) => store.close()),
this.primaryStore.close(),
...this.stores.map((store) => store.close()),
]);
}
/**
* Add a new remote store and set up synchronization
*/
public async addRemoteStore(remoteStore: BaseStore): Promise<void> {
this.remoteStores.push(remoteStore);
async addStore(remoteStore: BaseStore): Promise<void> {
this.stores.push(remoteStore);
// Propagate existing local events to the new remote
this.localStore.on('set', async (payload) => {
this.primaryStore.on('set', async (payload) => {
if (await remoteStore.has(payload.key)) return;
await remoteStore.set(payload.key, payload.value);
});
this.localStore.on('delete', async (payload) => {
await remoteStore.delete(payload.key);
this.primaryStore.on('delete', async (payload) => {
if (await remoteStore.has(payload.key)) {
await remoteStore.delete(payload.key);
}
});
this.localStore.on('clear', async () => {
this.primaryStore.on('clear', async () => {
await remoteStore.clear();
});
// Set up remote to local propagation
remoteStore.on('set', async (payload) => {
if (await this.localStore.has(payload.key)) return;
await this.localStore.set(payload.key, payload.value);
if (await this.primaryStore.has(payload.key)) return;
await this.primaryStore.set(payload.key, payload.value);
});
remoteStore.on('delete', async (payload) => {
if (await this.localStore.has(payload.key)) {
await this.localStore.delete(payload.key);
if (await this.primaryStore.has(payload.key)) {
await this.primaryStore.delete(payload.key);
}
});
remoteStore.on('clear', async () => {
await this.localStore.clear();
await this.primaryStore.clear();
});
// Sync with the new remote
await this.syncWithRemote(remoteStore);
}
private async syncWithRemote(remoteStore: BaseStore): Promise<void> {
const remoteKeys = await remoteStore.keys();
const localKeys = await this.localStore.keys();
const missingLocalKeys = remoteKeys.filter(
(key) => !localKeys.includes(key),
);
const missingRemoteKeys = localKeys.filter(
(key) => !remoteKeys.includes(key),
);
await Promise.all([
...missingLocalKeys.map(async (key) => {
const value = await remoteStore.get(key);
await this.localStore.set(key, value);
}),
...missingRemoteKeys.map(async (key) => {
const value = await this.localStore.get(key);
await remoteStore.set(key, value);
}),
]);
await this.syncStore(remoteStore);
}
async sync(): Promise<void> {
// Sync all remote stores in parallel
await Promise.all(
this.remoteStores.map(async (remoteStore) =>
this.syncWithRemote(remoteStore),
),
this.stores.map(async (remoteStore) => this.syncStore(remoteStore)),
);
}
private async syncStore(store: BaseStore): Promise<void> {
const storeKeys = await store.keys();
const primaryStoreKeys = await this.primaryStore.keys();
const missingLocalKeys = storeKeys.filter(
(key) => !primaryStoreKeys.includes(key),
);
const missingRemoteKeys = primaryStoreKeys.filter(
(key) => !storeKeys.includes(key),
);
await Promise.all([
...missingLocalKeys.map(async (key) => {
const value = await store.get(key);
await this.set(key, value);
}),
...missingRemoteKeys.map(async (key) => {
const value = await this.primaryStore.get(key);
await store.set(key, value);
}),
]);
}
}