Doc commit tag conventions

This commit is contained in:
Jim Hamill
2025-03-10 05:36:16 +00:00
parent 307650e9fc
commit 0346f3f79e
23 changed files with 1290 additions and 286 deletions
+55 -3
View File
@@ -66,6 +66,51 @@ export type PolicyFunction<T = any> = (
newActivity: Activity<string, T>,
) => Activity<string, T>;
/**
* Implements an event sourcing pattern for managing application state.
*
* Event sourcing is an architectural pattern where:
* - All changes to application state are stored as a sequence of immutable events
* - The current state is derived by replaying these events in chronological order
* - Events serve as both the source of truth and a complete audit log
*
* This implementation provides:
* - Eventually consistent state across distributed systems
* - Conflict resolution through configurable policies
* - Point-in-time state reconstruction capability
* - Separation between event storage and derived state (snapshot)
*
* Events (Activities) in this system:
* - Have a unique identifier
* - Include a timestamp for ordering
* - Contain a type and associated value
* - Are processed according to registered type-specific policies
*
* @example
* ```typescript
* // Create Activities manager with a store implementation
* const activities = new Activities(new MyBaseStore());
*
* // Register a custom policy for wallet name conflicts
* activities.registerPolicy('WalletName', (existing, newActivity) => {
* // Keep the most recent wallet name
* return existing?.timestamp! > newActivity.timestamp! ? existing : newActivity;
* });
*
* // Initialize by compiling snapshot from stored events
* await activities.compileSnapshot();
*
* // Add a new activity
* await activities.addActivity({
* type: 'WalletName',
* id: 'wallet123',
* value: 'My Primary Wallet'
* });
* ```
*
* @template SnapshotByType - Structured representation of the current system state
* @template PolicyFunction - Function type for conflict resolution strategies
*/
export class Activities {
// TODO: Consider splitting into its own mutable class: ActivitySnapshot.
// This might be cleaner as we'll likely want to be able to view a raw timeline of activities.
@@ -110,6 +155,10 @@ export class Activities {
storeKey: string,
activity: Activity<string, any>,
) {
if (!activity) {
return;
}
if (!activity.timestamp) {
console.warn('Activity without timestamp encountered:', storeKey);
return;
@@ -132,12 +181,12 @@ export class Activities {
const oldStoreKey = this.storeKeyMap.get(snapshotKey);
if (oldStoreKey) {
// Delete the old activity from the store
await this.store.delete(oldStoreKey);
await this.store.set(oldStoreKey, null);
}
this.storeKeyMap.set(snapshotKey, storeKey);
} else {
// If we kept the existing activity, delete the new activity from the store
await this.store.delete(storeKey);
await this.store.set(storeKey, null);
}
}
@@ -167,8 +216,11 @@ export class Activities {
const activities = await this.store.all<Activity<string, any>>();
const entries = Object.entries(activities);
// Filter for null payloads
const filteredEntries = entries.filter(([_, payload]) => payload);
// Sort activities by timestamp using UUID v7's embedded timestamp
const activitiesSorted = entries.sort(([keyA], [keyB]) => {
const activitiesSorted = filteredEntries.sort(([keyA], [keyB]) => {
const timestampA = getTimestampFromUUID(keyA);
const timestampB = getTimestampFromUUID(keyB);
return Number(timestampA - timestampB);