Last active
December 2, 2024 17:49
-
-
Save tnhu/63596787bd07591bcaea3d1d9b3af1a1 to your computer and use it in GitHub Desktop.
Base class providing chainable async actions with error handling
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* Base class providing chainable async actions with error handling | |
* @template T - The type of object being operated on | |
*/ | |
export abstract class ChainableActions<T> { | |
protected chain: Array<{ operation: string; task: () => Promise<void> }> = [] | |
protected error: any | null = null | |
protected subject: T | |
protected errorHandler: ((error: any) => void) | null = null | |
protected updateHandler: ((subject: T) => void) | null = null | |
constructor(subject: T) { | |
this.subject = subject | |
} | |
/** | |
* Create a new instance from a subject or a promise-returning function | |
* @param subject - The subject or a function that returns a Promise of the subject | |
*/ | |
static from<U, V extends ChainableActions<U>>(this: new (subject: U) => V, subject: U | (() => Promise<U>)): V { | |
if (typeof subject === 'function') { | |
// Create instance with a temporary subject | |
const instance = new this({} as U) | |
// Add the async initialization to the chain | |
instance.addToChain('initialize', async () => { | |
const resolvedSubject = await (subject as () => Promise<U>)() | |
instance.updateSubject(resolvedSubject) | |
}) | |
return instance | |
} | |
return new this(subject) | |
} | |
protected addToChain(operation: string, task: () => Promise<void>) { | |
if (this.error) return this | |
this.chain.push({ operation, task }) | |
return this | |
} | |
/** | |
* Update the subject being operated on | |
* @param subject - The new subject | |
*/ | |
protected updateSubject(subject: T): this { | |
this.subject = subject | |
this.updateHandler?.(subject) | |
return this | |
} | |
/** | |
* Handle subject updates | |
* @param handler - Function to handle subject updates | |
* @param executeImmediately - Whether to execute the handler immediately with current subject (default: false) | |
*/ | |
onUpdate(handler: (subject: Readonly<T>) => void, executeImmediately: boolean = false): this { | |
this.updateHandler = handler | |
if (executeImmediately) { | |
handler(this.subject) | |
} | |
return this | |
} | |
/** | |
* Handle errors in the chain | |
* @param handler - Function to handle the error | |
*/ | |
onError(handler: (error: any) => void): this { | |
this.errorHandler = handler | |
return this | |
} | |
/** | |
* Execute all chained actions | |
* Returns this to allow re-execution of the chain | |
*/ | |
async execute(): Promise<this> { | |
let currentOperation: string | null = null | |
// Execute each task in sequence | |
for (const { operation, task } of this.chain) { | |
currentOperation = operation | |
if (this.error) break | |
try { | |
await task() | |
} catch (error) { | |
this.error = error | |
break | |
} | |
} | |
if (this.error) { | |
this.errorHandler?.(this.error) | |
console.error(`Action failed: ${currentOperation}`, this.error) | |
// Reset error after handling | |
this.error = null | |
} | |
// Reset chain for potential re-execution | |
this.chain = [] | |
return this | |
} | |
/** | |
* Get the current subject state | |
*/ | |
getSubject(): Readonly<T> { | |
return this.subject | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { ChainableActions } from '../chainable-actions' | |
interface User { | |
id?: string | |
name?: string | |
email?: string | |
} | |
/** | |
* Chainable actions for users | |
*/ | |
export class UserActions extends ChainableActions<User> { | |
constructor(user: User) { | |
super(user) | |
} | |
/** | |
* Update the user being operated on | |
* @param user - The new user object | |
*/ | |
updateUser(user: User): this { | |
console.log('π Updating user:', { id: user.id, name: user.name }) | |
return this.updateSubject(user) | |
} | |
/** | |
* Register a new user | |
*/ | |
register(): this { | |
return this.addToChain('register user', async () => { | |
console.log('π Registering user:', { id: this.subject.id, name: this.subject.name }) | |
// TODO: Implement user registration logic | |
}) | |
} | |
/** | |
* Add a role to the user | |
* @param role - The role to add | |
*/ | |
addRole(role: string): this { | |
return this.addToChain(`add role '${role}'`, async () => { | |
console.log('π€ Adding role:', { role, userId: this.subject.id, userName: this.subject.name }) | |
// TODO: Implement role addition logic | |
}) | |
} | |
/** | |
* Add a permission to the user | |
* @param permission - The permission to add | |
*/ | |
addPermission(permission: string): this { | |
return this.addToChain(`add permission '${permission}'`, async () => { | |
console.log('π Adding permission:', { permission, userId: this.subject.id, userName: this.subject.name }) | |
// TODO: Implement permission addition logic | |
}) | |
} | |
/** | |
* Join an organization | |
* @param orgId - The organization ID to join | |
*/ | |
joinOrganization(orgId: string): this { | |
return this.addToChain(`join organization '${orgId}'`, async () => { | |
console.log('π’ Joining organization:', { orgId, userId: this.subject.id, userName: this.subject.name }) | |
// TODO: Implement organization joining logic | |
}) | |
} | |
/** | |
* Send an invitation to join an organization | |
* @param orgId - The organization ID to send invitation for | |
*/ | |
sendInvitationToJoinOrg(orgId: string): this { | |
return this.addToChain(`send invitation for organization '${orgId}'`, async () => { | |
console.log('βοΈ Sending organization invitation:', { | |
orgId, | |
userId: this.subject.id, | |
userName: this.subject.name | |
}) | |
// TODO: Implement invitation sending logic | |
}) | |
} | |
} | |
// Example usage: | |
// const user = new UserActions(userObject) | |
// const actions = await user | |
// .register() | |
// .addRole('admin') | |
// .onUpdate(user => { | |
// console.log('User updated:', user) | |
// }) | |
// .execute() | |
// | |
// // Chain can be executed again | |
// await actions | |
// .addPermission('write') | |
// .execute() | |
// Example of another class using ChainableActions: | |
// class DocumentActions extends ChainableActions<Document> { | |
// upload(): this { | |
// return this.addToChain('upload document', async () => { | |
// // Implementation | |
// }) | |
// } | |
// | |
// share(userId: string): this { | |
// return this.addToChain(`share with user ${userId}`, async () => { | |
// // Implementation | |
// }) | |
// } | |
// } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment