XO Integration Part II

This commit is contained in:
Jim Hamill
2025-05-08 08:05:46 +00:00
parent 476a535768
commit 1870877df5
13 changed files with 164 additions and 21 deletions
+24 -5
View File
@@ -25,10 +25,18 @@ export interface Activity {
// Define the snapshot structure
export type SnapshotByType = {
[type: string]: {
[id: string]: Activity;
[entityId: string]: Activity;
};
};
export type ActivitiesIndexedByKey<T extends Activity = Activity> = {
[entityId: string]: T;
};
export type ActivitiesIndexedByEntityId<T extends Activity = Activity> = {
[key: string]: T;
};
// Policy type definition
export type PolicyFunction = (
existingActivity: Activity | undefined,
@@ -36,6 +44,7 @@ export type PolicyFunction = (
) => Activity;
export type ActivitiesEvents = {
snapshotUpdated: undefined;
[key: `updated:${string}`]: { activity: Activity };
};
@@ -172,12 +181,17 @@ export class Activities extends EventEmitter<ActivitiesEvents> {
/**
* Get all activities of a specific type
*/
public getByType<T extends Activity>(type: T['key']): T[] {
const activities = this.snapshot[type];
public getByKey<T extends Activity>(
key: T['key'],
): ActivitiesIndexedByKey<T> {
const activities = this.snapshot[key];
if (!activities) {
return [];
return {};
}
return Object.values(activities) as T[];
// 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>;
}
/**
@@ -227,6 +241,9 @@ export class Activities extends EventEmitter<ActivitiesEvents> {
// Emit the updated activity.
this.emit(`updated:${activity.key}`, { activity });
// Emit an event indicating our snapshot has been updated.
this.emit('snapshotUpdated', undefined);
} else if (storeKey !== this.storeKeyMap.get(snapshotKey)) {
// If we kept the existing activity, delete the new activity from the store
// await this.store.set(storeKey, null);
@@ -261,6 +278,8 @@ export class Activities extends EventEmitter<ActivitiesEvents> {
await this.processActivity(storeKey, activity);
}
this.emit('snapshotUpdated', undefined);
return this.snapshot;
}
+1
View File
@@ -6,6 +6,7 @@ export type StorageOpts = {
};
export type StoreEvents = {
[key: `set:${string}`]: any;
set: { key: string; value: any };
delete: { key: string };
clear: undefined;
@@ -26,6 +26,10 @@ export class StorageLocalStorage extends BaseStorage {
const stores = new Set<string>();
// Iterate through localStorage to find all stores with our dbName prefix
// NOTE: I think this was me (Jim) just being stupid.
// We don't want to do this - we want to over-ride "on" to emit on first subscription.
// And then call super.on?
/*
for (let i = 0; i < localStorage.length; i++) {
const key = localStorage.key(i);
if (key && key.startsWith(prefix)) {
@@ -35,6 +39,7 @@ export class StorageLocalStorage extends BaseStorage {
}
}
}
*/
return Array.from(stores);
}
@@ -171,7 +176,15 @@ export class StoreLocalStorage extends BaseStore {
encryption?: Encrypter,
parentStorage?: StorageLocalStorage,
) {
return new this(dbName, storeName, encryption, parentStorage);
const store = new this(dbName, storeName, encryption, parentStorage);
// Emit events for all entries.
const allEntries = await store.all();
Object.entries(allEntries).forEach(([key, value]) =>
store.emit(`set:${key}`, value),
);
return store;
}
async all<T>() {
@@ -235,6 +248,7 @@ export class StoreLocalStorage extends BaseStore {
// LocalStorage can only store strings, so we convert the binary data
localStorage.setItem(fullKey, encryptedValue);
this.emit('set', { key, value });
this.emit(`set:${key}`, value);
}
async delete(key: string) {