XO Wallet Integration Part IV

This commit is contained in:
Jim Hamill
2025-05-27 23:17:41 +00:00
parent 25b58853a3
commit 2bfeed8587
15 changed files with 249 additions and 123 deletions
+85 -18
View File
@@ -23,18 +23,22 @@ export interface Activity {
}
// Define the snapshot structure
export type SnapshotByType = {
[type: string]: {
export type SnapshotState = {
[key: string]: {
[entityId: string]: Activity;
};
};
export type ActivitiesIndexedByKey<T extends Activity = Activity> = {
[entityId: string]: T;
export type SnapshotData<T extends Activity = Activity> = {
[keyOrEntityId: string]: T;
};
export type ActivitiesIndexedByEntityId<T extends Activity = Activity> = {
[key: string]: T;
export type SnapshotIndexedByKey<T extends Activity = Activity> = {
[key: string]: { [entityId: string]: T };
};
export type SnapshotIndexedByEntityId<T extends Activity = Activity> = {
[entityId: string]: { [key: string]: T };
};
// Policy type definition
@@ -45,6 +49,7 @@ export type PolicyFunction = (
export type ActivitiesEvents = {
snapshotUpdated: undefined;
updated: { activity: Activity };
[key: `updated:${string}`]: { activity: Activity };
};
@@ -79,9 +84,15 @@ export class Activities extends EventEmitter<ActivitiesEvents> {
// TODO: If you can refactor this around your new Storage mechanism, things become a lot simpler:
// 1. You don't need to define such a clunky snapshot format because you have indexes.
// 2. Caching of snapshots likely becomes possible (because you can use a storage adapter).
public snapshot: SnapshotByType = {};
private policies = new Map<string, PolicyFunction>();
private storeKeyMap = new Map<string, string>();
public snapshot: SnapshotState = {};
// Indexes.
// TODO: We want to be able to remove these in favor of the TODO note above.
// public snapshotIndexedByEntity: SnapshotIndexedByEntityId = {};
// public snapshotIndexedByKey: SnapshotIndexedByKey = {};
protected policies = new Map<string, PolicyFunction>();
protected storeKeyMap = new Map<string, string>();
/**
* Create an Activities instance from the given composite store.
@@ -93,7 +104,7 @@ export class Activities extends EventEmitter<ActivitiesEvents> {
return activities;
}
private constructor(store: StoreComposite) {
protected constructor(store: StoreComposite) {
super();
// Assign our dependencies.
@@ -167,6 +178,10 @@ export class Activities extends EventEmitter<ActivitiesEvents> {
return removedActivity;
}
public getSnapshot() {
return this.snapshot;
}
/**
* Get a specific activity by type and id with type inference
*/
@@ -178,12 +193,23 @@ export class Activities extends EventEmitter<ActivitiesEvents> {
return activity as T | undefined;
}
public getByEntity(entityId: string = '') {
return Object.keys(this.snapshot).reduce(
(result, key) => {
const activity = this.snapshot[key][entityId];
if (activity) {
result[key] = activity;
}
return result;
},
{} as Record<string, Activity>,
);
}
/**
* Get all activities of a specific type
*/
public getByKey<T extends Activity>(
key: T['key'],
): ActivitiesIndexedByKey<T> {
public getByKey<T extends Activity>(key: T['key']): SnapshotData<T> {
const activities = this.snapshot[key];
if (!activities) {
return {};
@@ -191,9 +217,25 @@ export class Activities extends EventEmitter<ActivitiesEvents> {
// NOTE: We want to return a new object, not a reference to our current object.
// But we only need to "clone" the first level as each activity itself is immutable.
return { ...activities } as ActivitiesIndexedByKey<T>;
return { ...activities } as SnapshotData<T>;
}
/*
public getByEntityId<T extends Activity>(
entityId: string = '',
): SnapshotData<T> {
const activities = this.snapshotIndexedByEntity[entityId];
if (!activities) {
return {};
}
// NOTE: We want to return a new object, not a reference to our current object.
// But we only need to "clone" the first level as each activity itself is immutable.
return { ...activities } as SnapshotData<T>;
}
*/
/**
* Register a policy for handling conflicts between activities of the same type
* @param type Activity type to register policy for
@@ -239,6 +281,9 @@ export class Activities extends EventEmitter<ActivitiesEvents> {
}
this.storeKeyMap.set(snapshotKey, storeKey);
// Emit the updated activity.
this.emit('updated', { activity });
// Emit the updated activity.
this.emit(`updated:${activity.key}`, { activity });
@@ -254,7 +299,7 @@ export class Activities extends EventEmitter<ActivitiesEvents> {
* Compile a full snapshot from scratch
* This happens automatically on initialization
*/
private async compileSnapshot(): Promise<SnapshotByType> {
private async compileSnapshot(): Promise<SnapshotState> {
// Clear existing snapshot
this.snapshot = {};
this.storeKeyMap.clear();
@@ -297,11 +342,33 @@ export class Activities extends EventEmitter<ActivitiesEvents> {
* Set an activity in the snapshot, creating necessary object structure
*/
private setActivityInSnapshot(activity: Activity): void {
if (!this.snapshot[activity.key]) {
this.snapshot[activity.key] = {};
// Assign the key for convenience.
const key = activity.key;
// Assign the entityId to the provided entityId - or to the root ("") entity if none provided.
const entityId = activity.entityId || '';
if (!this.snapshot[key]) {
this.snapshot[key] = {};
}
this.snapshot[activity.key][activity.entityId || ''] = activity;
this.snapshot[key][entityId] = activity;
/*
// Index the activity by entityId:key.
if (!this.snapshotIndexedByEntity[entityId]) {
this.snapshot[entityId] = {};
}
this.snapshotIndexedByEntity[entityId][key] = activity;
// Index the activity by key:entityId.
if (!this.snapshotIndexedByKey[key]) {
this.snapshot[key] = {};
}
this.snapshotIndexedByKey[key][entityId] = activity;
*/
}
/**