Files
esengine/packages/framework/transaction/src/integration/RoomTransactionMixin.ts

175 lines
4.6 KiB
TypeScript
Raw Normal View History

/**
* @zh Room
* @en Room transaction extension
*/
import type {
ITransactionStorage,
ITransactionContext,
TransactionOptions,
TransactionResult
} from '../core/types.js';
import { TransactionManager } from '../core/TransactionManager.js';
/**
* @zh Room
* @en Transaction Room configuration
*/
export interface TransactionRoomConfig {
/**
* @zh
* @en Storage instance
*/
storage?: ITransactionStorage
/**
* @zh
* @en Default timeout in milliseconds
*/
defaultTimeout?: number
/**
* @zh ID
* @en Server ID
*/
serverId?: string
}
/**
* @zh Room
* @en Transaction Room interface
*/
export interface ITransactionRoom {
/**
* @zh
* @en Transaction manager
*/
readonly transactions: TransactionManager
/**
* @zh
* @en Begin transaction
*/
beginTransaction(options?: TransactionOptions): ITransactionContext
/**
* @zh
* @en Run transaction
*/
runTransaction<T = unknown>(
builder: (ctx: ITransactionContext) => void | Promise<void>,
options?: TransactionOptions
): Promise<TransactionResult<T>>
}
/**
* @zh Room mixin
* @en Create transaction Room mixin
*
* @example
* ```typescript
* import { Room } from '@esengine/server'
* import { withTransactions, RedisStorage } from '@esengine/transaction'
*
* class GameRoom extends withTransactions(Room, {
* storage: new RedisStorage({ client: redisClient }),
* }) {
* async handleBuy(itemId: string, player: Player) {
* const result = await this.runTransaction((tx) => {
* tx.addOperation(new CurrencyOperation({
* type: 'deduct',
* playerId: player.id,
* currency: 'gold',
* amount: 100,
* }))
* })
*
* if (result.success) {
* player.send('buy_success', { itemId })
* }
* }
* }
* ```
*/
export function withTransactions<TBase extends new (...args: any[]) => any>(
Base: TBase,
config: TransactionRoomConfig = {}
): TBase & (new (...args: any[]) => ITransactionRoom) {
return class TransactionRoom extends Base implements ITransactionRoom {
private _transactionManager: TransactionManager;
constructor(...args: any[]) {
super(...args);
this._transactionManager = new TransactionManager({
storage: config.storage,
defaultTimeout: config.defaultTimeout,
serverId: config.serverId
});
}
get transactions(): TransactionManager {
return this._transactionManager;
}
beginTransaction(options?: TransactionOptions): ITransactionContext {
return this._transactionManager.begin(options);
}
runTransaction<T = unknown>(
builder: (ctx: ITransactionContext) => void | Promise<void>,
options?: TransactionOptions
): Promise<TransactionResult<T>> {
return this._transactionManager.run<T>(builder, options);
}
};
}
/**
* @zh Room
* @en Transaction Room abstract base class
*
* @zh 使使 withTransactions mixin
* @en Can be extended directly or use withTransactions mixin
*
* @example
* ```typescript
* class GameRoom extends TransactionRoom {
* constructor() {
* super({ storage: new RedisStorage({ client: redisClient }) })
* }
*
* async handleTrade(data: TradeData, player: Player) {
* const result = await this.runTransaction((tx) => {
* // 添加交易操作
* })
* }
* }
* ```
*/
export abstract class TransactionRoom implements ITransactionRoom {
private _transactionManager: TransactionManager;
constructor(config: TransactionRoomConfig = {}) {
this._transactionManager = new TransactionManager({
storage: config.storage,
defaultTimeout: config.defaultTimeout,
serverId: config.serverId
});
}
get transactions(): TransactionManager {
return this._transactionManager;
}
beginTransaction(options?: TransactionOptions): ITransactionContext {
return this._transactionManager.begin(options);
}
runTransaction<T = unknown>(
builder: (ctx: ITransactionContext) => void | Promise<void>,
options?: TransactionOptions
): Promise<TransactionResult<T>> {
return this._transactionManager.run<T>(builder, options);
}
}