@esengine/transaction 2.0.7 → 2.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (41) hide show
  1. package/dist/core/TransactionContext.d.ts +79 -0
  2. package/dist/core/TransactionContext.d.ts.map +1 -0
  3. package/dist/core/TransactionManager.d.ts +104 -0
  4. package/dist/core/TransactionManager.d.ts.map +1 -0
  5. package/dist/core/index.d.ts +8 -0
  6. package/dist/core/index.d.ts.map +1 -0
  7. package/dist/core/types.d.ts +393 -0
  8. package/dist/core/types.d.ts.map +1 -0
  9. package/dist/distributed/SagaOrchestrator.d.ts +173 -0
  10. package/dist/distributed/SagaOrchestrator.d.ts.map +1 -0
  11. package/dist/distributed/index.d.ts +6 -0
  12. package/dist/distributed/index.d.ts.map +1 -0
  13. package/dist/index.d.ts +56 -0
  14. package/dist/index.d.ts.map +1 -0
  15. package/dist/index.js +1621 -0
  16. package/dist/index.js.map +1 -0
  17. package/dist/integration/RoomTransactionMixin.d.ts +108 -0
  18. package/dist/integration/RoomTransactionMixin.d.ts.map +1 -0
  19. package/dist/integration/index.d.ts +6 -0
  20. package/dist/integration/index.d.ts.map +1 -0
  21. package/dist/operations/BaseOperation.d.ts +43 -0
  22. package/dist/operations/BaseOperation.d.ts.map +1 -0
  23. package/dist/operations/CurrencyOperation.d.ts +122 -0
  24. package/dist/operations/CurrencyOperation.d.ts.map +1 -0
  25. package/dist/operations/InventoryOperation.d.ts +152 -0
  26. package/dist/operations/InventoryOperation.d.ts.map +1 -0
  27. package/dist/operations/TradeOperation.d.ts +155 -0
  28. package/dist/operations/TradeOperation.d.ts.map +1 -0
  29. package/dist/operations/index.d.ts +9 -0
  30. package/dist/operations/index.d.ts.map +1 -0
  31. package/dist/storage/MemoryStorage.d.ts +63 -0
  32. package/dist/storage/MemoryStorage.d.ts.map +1 -0
  33. package/dist/storage/MongoStorage.d.ts +118 -0
  34. package/dist/storage/MongoStorage.d.ts.map +1 -0
  35. package/dist/storage/RedisStorage.d.ts +125 -0
  36. package/dist/storage/RedisStorage.d.ts.map +1 -0
  37. package/dist/storage/index.d.ts +8 -0
  38. package/dist/storage/index.d.ts.map +1 -0
  39. package/dist/tokens.d.ts +17 -0
  40. package/dist/tokens.d.ts.map +1 -0
  41. package/package.json +2 -2
package/dist/index.js ADDED
@@ -0,0 +1,1621 @@
1
+ var __defProp = Object.defineProperty;
2
+ var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
3
+ var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
4
+ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
5
+
6
+ // src/core/TransactionContext.ts
7
+ function generateId() {
8
+ return `tx_${Date.now().toString(36)}_${Math.random().toString(36).substring(2, 11)}`;
9
+ }
10
+ __name(generateId, "generateId");
11
+ var _TransactionContext = class _TransactionContext {
12
+ constructor(options = {}) {
13
+ __publicField(this, "_id");
14
+ __publicField(this, "_state", "pending");
15
+ __publicField(this, "_timeout");
16
+ __publicField(this, "_operations", []);
17
+ __publicField(this, "_storage");
18
+ __publicField(this, "_metadata");
19
+ __publicField(this, "_contextData", /* @__PURE__ */ new Map());
20
+ __publicField(this, "_startTime", 0);
21
+ __publicField(this, "_distributed");
22
+ this._id = generateId();
23
+ this._timeout = options.timeout ?? 3e4;
24
+ this._storage = options.storage ?? null;
25
+ this._metadata = options.metadata ?? {};
26
+ this._distributed = options.distributed ?? false;
27
+ }
28
+ // =========================================================================
29
+ // 只读属性 | Readonly properties
30
+ // =========================================================================
31
+ get id() {
32
+ return this._id;
33
+ }
34
+ get state() {
35
+ return this._state;
36
+ }
37
+ get timeout() {
38
+ return this._timeout;
39
+ }
40
+ get operations() {
41
+ return this._operations;
42
+ }
43
+ get storage() {
44
+ return this._storage;
45
+ }
46
+ get metadata() {
47
+ return this._metadata;
48
+ }
49
+ // =========================================================================
50
+ // 公共方法 | Public methods
51
+ // =========================================================================
52
+ /**
53
+ * @zh 添加操作
54
+ * @en Add operation
55
+ */
56
+ addOperation(operation) {
57
+ if (this._state !== "pending") {
58
+ throw new Error(`Cannot add operation to transaction in state: ${this._state}`);
59
+ }
60
+ this._operations.push(operation);
61
+ return this;
62
+ }
63
+ /**
64
+ * @zh 执行事务
65
+ * @en Execute transaction
66
+ */
67
+ async execute() {
68
+ if (this._state !== "pending") {
69
+ return {
70
+ success: false,
71
+ transactionId: this._id,
72
+ results: [],
73
+ error: `Transaction already in state: ${this._state}`,
74
+ duration: 0
75
+ };
76
+ }
77
+ this._startTime = Date.now();
78
+ this._state = "executing";
79
+ const results = [];
80
+ let executedCount = 0;
81
+ try {
82
+ await this._saveLog();
83
+ for (let i = 0; i < this._operations.length; i++) {
84
+ if (this._isTimedOut()) {
85
+ throw new Error("Transaction timed out");
86
+ }
87
+ const op = this._operations[i];
88
+ const isValid = await op.validate(this);
89
+ if (!isValid) {
90
+ throw new Error(`Validation failed for operation: ${op.name}`);
91
+ }
92
+ const result = await op.execute(this);
93
+ results.push(result);
94
+ executedCount++;
95
+ await this._updateOperationLog(i, "executed");
96
+ if (!result.success) {
97
+ throw new Error(result.error ?? `Operation ${op.name} failed`);
98
+ }
99
+ }
100
+ this._state = "committed";
101
+ await this._updateTransactionState("committed");
102
+ return {
103
+ success: true,
104
+ transactionId: this._id,
105
+ results,
106
+ data: this._collectResultData(results),
107
+ duration: Date.now() - this._startTime
108
+ };
109
+ } catch (error) {
110
+ const errorMessage = error instanceof Error ? error.message : String(error);
111
+ await this._compensate(executedCount - 1);
112
+ return {
113
+ success: false,
114
+ transactionId: this._id,
115
+ results,
116
+ error: errorMessage,
117
+ duration: Date.now() - this._startTime
118
+ };
119
+ }
120
+ }
121
+ /**
122
+ * @zh 手动回滚事务
123
+ * @en Manually rollback transaction
124
+ */
125
+ async rollback() {
126
+ if (this._state === "committed" || this._state === "rolledback") {
127
+ return;
128
+ }
129
+ await this._compensate(this._operations.length - 1);
130
+ }
131
+ /**
132
+ * @zh 获取上下文数据
133
+ * @en Get context data
134
+ */
135
+ get(key) {
136
+ return this._contextData.get(key);
137
+ }
138
+ /**
139
+ * @zh 设置上下文数据
140
+ * @en Set context data
141
+ */
142
+ set(key, value) {
143
+ this._contextData.set(key, value);
144
+ }
145
+ // =========================================================================
146
+ // 私有方法 | Private methods
147
+ // =========================================================================
148
+ _isTimedOut() {
149
+ return Date.now() - this._startTime > this._timeout;
150
+ }
151
+ async _compensate(fromIndex) {
152
+ this._state = "rolledback";
153
+ for (let i = fromIndex; i >= 0; i--) {
154
+ const op = this._operations[i];
155
+ try {
156
+ await op.compensate(this);
157
+ await this._updateOperationLog(i, "compensated");
158
+ } catch (error) {
159
+ const errorMessage = error instanceof Error ? error.message : String(error);
160
+ await this._updateOperationLog(i, "failed", errorMessage);
161
+ }
162
+ }
163
+ await this._updateTransactionState("rolledback");
164
+ }
165
+ async _saveLog() {
166
+ if (!this._storage) return;
167
+ const log = {
168
+ id: this._id,
169
+ state: this._state,
170
+ createdAt: this._startTime,
171
+ updatedAt: this._startTime,
172
+ timeout: this._timeout,
173
+ operations: this._operations.map((op) => ({
174
+ name: op.name,
175
+ data: op.data,
176
+ state: "pending"
177
+ })),
178
+ metadata: this._metadata,
179
+ distributed: this._distributed
180
+ };
181
+ await this._storage.saveTransaction(log);
182
+ }
183
+ async _updateTransactionState(state) {
184
+ this._state = state;
185
+ if (this._storage) {
186
+ await this._storage.updateTransactionState(this._id, state);
187
+ }
188
+ }
189
+ async _updateOperationLog(index, state, error) {
190
+ if (this._storage) {
191
+ await this._storage.updateOperationState(this._id, index, state, error);
192
+ }
193
+ }
194
+ _collectResultData(results) {
195
+ const data = {};
196
+ for (const result of results) {
197
+ if (result.data !== void 0) {
198
+ Object.assign(data, result.data);
199
+ }
200
+ }
201
+ return Object.keys(data).length > 0 ? data : void 0;
202
+ }
203
+ };
204
+ __name(_TransactionContext, "TransactionContext");
205
+ var TransactionContext = _TransactionContext;
206
+ function createTransactionContext(options = {}) {
207
+ return new TransactionContext(options);
208
+ }
209
+ __name(createTransactionContext, "createTransactionContext");
210
+
211
+ // src/core/TransactionManager.ts
212
+ var _TransactionManager = class _TransactionManager {
213
+ constructor(config = {}) {
214
+ __publicField(this, "_storage");
215
+ __publicField(this, "_defaultTimeout");
216
+ __publicField(this, "_serverId");
217
+ __publicField(this, "_autoRecover");
218
+ __publicField(this, "_activeTransactions", /* @__PURE__ */ new Map());
219
+ this._storage = config.storage ?? null;
220
+ this._defaultTimeout = config.defaultTimeout ?? 3e4;
221
+ this._serverId = config.serverId ?? this._generateServerId();
222
+ this._autoRecover = config.autoRecover ?? true;
223
+ }
224
+ // =========================================================================
225
+ // 只读属性 | Readonly properties
226
+ // =========================================================================
227
+ /**
228
+ * @zh 服务器 ID
229
+ * @en Server ID
230
+ */
231
+ get serverId() {
232
+ return this._serverId;
233
+ }
234
+ /**
235
+ * @zh 存储实例
236
+ * @en Storage instance
237
+ */
238
+ get storage() {
239
+ return this._storage;
240
+ }
241
+ /**
242
+ * @zh 活跃事务数量
243
+ * @en Active transaction count
244
+ */
245
+ get activeCount() {
246
+ return this._activeTransactions.size;
247
+ }
248
+ // =========================================================================
249
+ // 公共方法 | Public methods
250
+ // =========================================================================
251
+ /**
252
+ * @zh 开始新事务
253
+ * @en Begin new transaction
254
+ *
255
+ * @param options - @zh 事务选项 @en Transaction options
256
+ * @returns @zh 事务上下文 @en Transaction context
257
+ */
258
+ begin(options = {}) {
259
+ const ctx = new TransactionContext({
260
+ timeout: options.timeout ?? this._defaultTimeout,
261
+ storage: this._storage ?? void 0,
262
+ metadata: {
263
+ ...options.metadata,
264
+ serverId: this._serverId
265
+ },
266
+ distributed: options.distributed
267
+ });
268
+ this._activeTransactions.set(ctx.id, ctx);
269
+ return ctx;
270
+ }
271
+ /**
272
+ * @zh 执行事务(便捷方法)
273
+ * @en Execute transaction (convenience method)
274
+ *
275
+ * @param builder - @zh 事务构建函数 @en Transaction builder function
276
+ * @param options - @zh 事务选项 @en Transaction options
277
+ * @returns @zh 事务结果 @en Transaction result
278
+ */
279
+ async run(builder, options = {}) {
280
+ const ctx = this.begin(options);
281
+ try {
282
+ await builder(ctx);
283
+ const result = await ctx.execute();
284
+ return result;
285
+ } finally {
286
+ this._activeTransactions.delete(ctx.id);
287
+ }
288
+ }
289
+ /**
290
+ * @zh 获取活跃事务
291
+ * @en Get active transaction
292
+ */
293
+ getTransaction(id) {
294
+ return this._activeTransactions.get(id);
295
+ }
296
+ /**
297
+ * @zh 恢复未完成的事务
298
+ * @en Recover pending transactions
299
+ */
300
+ async recover() {
301
+ if (!this._storage) return 0;
302
+ const pendingTransactions = await this._storage.getPendingTransactions(this._serverId);
303
+ let recoveredCount = 0;
304
+ for (const log of pendingTransactions) {
305
+ try {
306
+ await this._recoverTransaction(log);
307
+ recoveredCount++;
308
+ } catch (error) {
309
+ console.error(`Failed to recover transaction ${log.id}:`, error);
310
+ }
311
+ }
312
+ return recoveredCount;
313
+ }
314
+ /**
315
+ * @zh 获取分布式锁
316
+ * @en Acquire distributed lock
317
+ */
318
+ async acquireLock(key, ttl = 1e4) {
319
+ if (!this._storage) return null;
320
+ return this._storage.acquireLock(key, ttl);
321
+ }
322
+ /**
323
+ * @zh 释放分布式锁
324
+ * @en Release distributed lock
325
+ */
326
+ async releaseLock(key, token) {
327
+ if (!this._storage) return false;
328
+ return this._storage.releaseLock(key, token);
329
+ }
330
+ /**
331
+ * @zh 使用分布式锁执行
332
+ * @en Execute with distributed lock
333
+ */
334
+ async withLock(key, fn, ttl = 1e4) {
335
+ const token = await this.acquireLock(key, ttl);
336
+ if (!token) {
337
+ throw new Error(`Failed to acquire lock for key: ${key}`);
338
+ }
339
+ try {
340
+ return await fn();
341
+ } finally {
342
+ await this.releaseLock(key, token);
343
+ }
344
+ }
345
+ /**
346
+ * @zh 清理已完成的事务日志
347
+ * @en Clean up completed transaction logs
348
+ */
349
+ async cleanup(beforeTimestamp) {
350
+ if (!this._storage) return 0;
351
+ const timestamp = beforeTimestamp ?? Date.now() - 24 * 60 * 60 * 1e3;
352
+ const pendingTransactions = await this._storage.getPendingTransactions();
353
+ let cleanedCount = 0;
354
+ for (const log of pendingTransactions) {
355
+ if (log.createdAt < timestamp && (log.state === "committed" || log.state === "rolledback")) {
356
+ await this._storage.deleteTransaction(log.id);
357
+ cleanedCount++;
358
+ }
359
+ }
360
+ return cleanedCount;
361
+ }
362
+ // =========================================================================
363
+ // 私有方法 | Private methods
364
+ // =========================================================================
365
+ _generateServerId() {
366
+ return `server_${Date.now().toString(36)}_${Math.random().toString(36).substring(2, 8)}`;
367
+ }
368
+ async _recoverTransaction(log) {
369
+ if (log.state === "executing") {
370
+ const executedOps = log.operations.filter((op) => op.state === "executed");
371
+ if (executedOps.length > 0 && this._storage) {
372
+ for (let i = executedOps.length - 1; i >= 0; i--) {
373
+ await this._storage.updateOperationState(log.id, i, "compensated");
374
+ }
375
+ await this._storage.updateTransactionState(log.id, "rolledback");
376
+ } else {
377
+ await this._storage?.updateTransactionState(log.id, "failed");
378
+ }
379
+ }
380
+ }
381
+ };
382
+ __name(_TransactionManager, "TransactionManager");
383
+ var TransactionManager = _TransactionManager;
384
+ function createTransactionManager(config = {}) {
385
+ return new TransactionManager(config);
386
+ }
387
+ __name(createTransactionManager, "createTransactionManager");
388
+
389
+ // src/storage/MemoryStorage.ts
390
+ var _MemoryStorage = class _MemoryStorage {
391
+ constructor(config = {}) {
392
+ __publicField(this, "_transactions", /* @__PURE__ */ new Map());
393
+ __publicField(this, "_data", /* @__PURE__ */ new Map());
394
+ __publicField(this, "_locks", /* @__PURE__ */ new Map());
395
+ __publicField(this, "_maxTransactions");
396
+ this._maxTransactions = config.maxTransactions ?? 1e3;
397
+ }
398
+ // =========================================================================
399
+ // 分布式锁 | Distributed Lock
400
+ // =========================================================================
401
+ async acquireLock(key, ttl) {
402
+ this._cleanExpiredLocks();
403
+ const existing = this._locks.get(key);
404
+ if (existing && existing.expireAt > Date.now()) {
405
+ return null;
406
+ }
407
+ const token = `lock_${Date.now()}_${Math.random().toString(36).substring(2)}`;
408
+ this._locks.set(key, {
409
+ token,
410
+ expireAt: Date.now() + ttl
411
+ });
412
+ return token;
413
+ }
414
+ async releaseLock(key, token) {
415
+ const lock = this._locks.get(key);
416
+ if (!lock || lock.token !== token) {
417
+ return false;
418
+ }
419
+ this._locks.delete(key);
420
+ return true;
421
+ }
422
+ // =========================================================================
423
+ // 事务日志 | Transaction Log
424
+ // =========================================================================
425
+ async saveTransaction(tx) {
426
+ if (this._transactions.size >= this._maxTransactions) {
427
+ this._cleanOldTransactions();
428
+ }
429
+ this._transactions.set(tx.id, {
430
+ ...tx
431
+ });
432
+ }
433
+ async getTransaction(id) {
434
+ const tx = this._transactions.get(id);
435
+ return tx ? {
436
+ ...tx
437
+ } : null;
438
+ }
439
+ async updateTransactionState(id, state) {
440
+ const tx = this._transactions.get(id);
441
+ if (tx) {
442
+ tx.state = state;
443
+ tx.updatedAt = Date.now();
444
+ }
445
+ }
446
+ async updateOperationState(transactionId, operationIndex, state, error) {
447
+ const tx = this._transactions.get(transactionId);
448
+ if (tx && tx.operations[operationIndex]) {
449
+ tx.operations[operationIndex].state = state;
450
+ if (error) {
451
+ tx.operations[operationIndex].error = error;
452
+ }
453
+ if (state === "executed") {
454
+ tx.operations[operationIndex].executedAt = Date.now();
455
+ } else if (state === "compensated") {
456
+ tx.operations[operationIndex].compensatedAt = Date.now();
457
+ }
458
+ tx.updatedAt = Date.now();
459
+ }
460
+ }
461
+ async getPendingTransactions(serverId) {
462
+ const result = [];
463
+ for (const tx of this._transactions.values()) {
464
+ if (tx.state === "pending" || tx.state === "executing") {
465
+ if (!serverId || tx.metadata?.serverId === serverId) {
466
+ result.push({
467
+ ...tx
468
+ });
469
+ }
470
+ }
471
+ }
472
+ return result;
473
+ }
474
+ async deleteTransaction(id) {
475
+ this._transactions.delete(id);
476
+ }
477
+ // =========================================================================
478
+ // 数据操作 | Data Operations
479
+ // =========================================================================
480
+ async get(key) {
481
+ this._cleanExpiredData();
482
+ const entry = this._data.get(key);
483
+ if (!entry) return null;
484
+ if (entry.expireAt && entry.expireAt < Date.now()) {
485
+ this._data.delete(key);
486
+ return null;
487
+ }
488
+ return entry.value;
489
+ }
490
+ async set(key, value, ttl) {
491
+ this._data.set(key, {
492
+ value,
493
+ expireAt: ttl ? Date.now() + ttl : void 0
494
+ });
495
+ }
496
+ async delete(key) {
497
+ return this._data.delete(key);
498
+ }
499
+ // =========================================================================
500
+ // 辅助方法 | Helper methods
501
+ // =========================================================================
502
+ /**
503
+ * @zh 清空所有数据(测试用)
504
+ * @en Clear all data (for testing)
505
+ */
506
+ clear() {
507
+ this._transactions.clear();
508
+ this._data.clear();
509
+ this._locks.clear();
510
+ }
511
+ /**
512
+ * @zh 获取事务数量
513
+ * @en Get transaction count
514
+ */
515
+ get transactionCount() {
516
+ return this._transactions.size;
517
+ }
518
+ _cleanExpiredLocks() {
519
+ const now = Date.now();
520
+ for (const [key, lock] of this._locks) {
521
+ if (lock.expireAt < now) {
522
+ this._locks.delete(key);
523
+ }
524
+ }
525
+ }
526
+ _cleanExpiredData() {
527
+ const now = Date.now();
528
+ for (const [key, entry] of this._data) {
529
+ if (entry.expireAt && entry.expireAt < now) {
530
+ this._data.delete(key);
531
+ }
532
+ }
533
+ }
534
+ _cleanOldTransactions() {
535
+ const sorted = Array.from(this._transactions.entries()).sort((a, b) => a[1].createdAt - b[1].createdAt);
536
+ const toRemove = sorted.slice(0, Math.floor(this._maxTransactions * 0.2)).filter(([_, tx]) => tx.state === "committed" || tx.state === "rolledback");
537
+ for (const [id] of toRemove) {
538
+ this._transactions.delete(id);
539
+ }
540
+ }
541
+ };
542
+ __name(_MemoryStorage, "MemoryStorage");
543
+ var MemoryStorage = _MemoryStorage;
544
+ function createMemoryStorage(config = {}) {
545
+ return new MemoryStorage(config);
546
+ }
547
+ __name(createMemoryStorage, "createMemoryStorage");
548
+
549
+ // src/storage/RedisStorage.ts
550
+ var LOCK_SCRIPT = `
551
+ if redis.call("get", KEYS[1]) == ARGV[1] then
552
+ return redis.call("del", KEYS[1])
553
+ else
554
+ return 0
555
+ end
556
+ `;
557
+ var _RedisStorage = class _RedisStorage {
558
+ constructor(config) {
559
+ __publicField(this, "_client", null);
560
+ __publicField(this, "_factory");
561
+ __publicField(this, "_prefix");
562
+ __publicField(this, "_transactionTTL");
563
+ __publicField(this, "_closed", false);
564
+ this._factory = config.factory;
565
+ this._prefix = config.prefix ?? "tx:";
566
+ this._transactionTTL = config.transactionTTL ?? 86400;
567
+ }
568
+ // =========================================================================
569
+ // 生命周期 | Lifecycle
570
+ // =========================================================================
571
+ /**
572
+ * @zh 获取 Redis 客户端(惰性连接)
573
+ * @en Get Redis client (lazy connection)
574
+ */
575
+ async _getClient() {
576
+ if (this._closed) {
577
+ throw new Error("RedisStorage is closed");
578
+ }
579
+ if (!this._client) {
580
+ this._client = await this._factory();
581
+ }
582
+ return this._client;
583
+ }
584
+ /**
585
+ * @zh 关闭存储连接
586
+ * @en Close storage connection
587
+ */
588
+ async close() {
589
+ if (this._closed) return;
590
+ this._closed = true;
591
+ if (this._client) {
592
+ await this._client.quit();
593
+ this._client = null;
594
+ }
595
+ }
596
+ /**
597
+ * @zh 支持 await using 语法
598
+ * @en Support await using syntax
599
+ */
600
+ async [Symbol.asyncDispose]() {
601
+ await this.close();
602
+ }
603
+ // =========================================================================
604
+ // 分布式锁 | Distributed Lock
605
+ // =========================================================================
606
+ async acquireLock(key, ttl) {
607
+ const client = await this._getClient();
608
+ const lockKey = `${this._prefix}lock:${key}`;
609
+ const token = `${Date.now()}_${Math.random().toString(36).substring(2)}`;
610
+ const ttlSeconds = Math.ceil(ttl / 1e3);
611
+ const result = await client.set(lockKey, token, "NX", "EX", String(ttlSeconds));
612
+ return result === "OK" ? token : null;
613
+ }
614
+ async releaseLock(key, token) {
615
+ const client = await this._getClient();
616
+ const lockKey = `${this._prefix}lock:${key}`;
617
+ const result = await client.eval(LOCK_SCRIPT, 1, lockKey, token);
618
+ return result === 1;
619
+ }
620
+ // =========================================================================
621
+ // 事务日志 | Transaction Log
622
+ // =========================================================================
623
+ async saveTransaction(tx) {
624
+ const client = await this._getClient();
625
+ const key = `${this._prefix}tx:${tx.id}`;
626
+ await client.set(key, JSON.stringify(tx));
627
+ await client.expire(key, this._transactionTTL);
628
+ if (tx.metadata?.serverId) {
629
+ const serverKey = `${this._prefix}server:${tx.metadata.serverId}:txs`;
630
+ await client.hset(serverKey, tx.id, String(tx.createdAt));
631
+ }
632
+ }
633
+ async getTransaction(id) {
634
+ const client = await this._getClient();
635
+ const key = `${this._prefix}tx:${id}`;
636
+ const data = await client.get(key);
637
+ return data ? JSON.parse(data) : null;
638
+ }
639
+ async updateTransactionState(id, state) {
640
+ const tx = await this.getTransaction(id);
641
+ if (tx) {
642
+ tx.state = state;
643
+ tx.updatedAt = Date.now();
644
+ await this.saveTransaction(tx);
645
+ }
646
+ }
647
+ async updateOperationState(transactionId, operationIndex, state, error) {
648
+ const tx = await this.getTransaction(transactionId);
649
+ if (tx && tx.operations[operationIndex]) {
650
+ tx.operations[operationIndex].state = state;
651
+ if (error) {
652
+ tx.operations[operationIndex].error = error;
653
+ }
654
+ if (state === "executed") {
655
+ tx.operations[operationIndex].executedAt = Date.now();
656
+ } else if (state === "compensated") {
657
+ tx.operations[operationIndex].compensatedAt = Date.now();
658
+ }
659
+ tx.updatedAt = Date.now();
660
+ await this.saveTransaction(tx);
661
+ }
662
+ }
663
+ async getPendingTransactions(serverId) {
664
+ const client = await this._getClient();
665
+ const result = [];
666
+ if (serverId) {
667
+ const serverKey = `${this._prefix}server:${serverId}:txs`;
668
+ const txIds = await client.hgetall(serverKey);
669
+ for (const id of Object.keys(txIds)) {
670
+ const tx = await this.getTransaction(id);
671
+ if (tx && (tx.state === "pending" || tx.state === "executing")) {
672
+ result.push(tx);
673
+ }
674
+ }
675
+ } else {
676
+ const pattern = `${this._prefix}tx:*`;
677
+ const keys = await client.keys(pattern);
678
+ for (const key of keys) {
679
+ const data = await client.get(key);
680
+ if (data) {
681
+ const tx = JSON.parse(data);
682
+ if (tx.state === "pending" || tx.state === "executing") {
683
+ result.push(tx);
684
+ }
685
+ }
686
+ }
687
+ }
688
+ return result;
689
+ }
690
+ async deleteTransaction(id) {
691
+ const client = await this._getClient();
692
+ const key = `${this._prefix}tx:${id}`;
693
+ const tx = await this.getTransaction(id);
694
+ await client.del(key);
695
+ if (tx?.metadata?.serverId) {
696
+ const serverKey = `${this._prefix}server:${tx.metadata.serverId}:txs`;
697
+ await client.hdel(serverKey, id);
698
+ }
699
+ }
700
+ // =========================================================================
701
+ // 数据操作 | Data Operations
702
+ // =========================================================================
703
+ async get(key) {
704
+ const client = await this._getClient();
705
+ const fullKey = `${this._prefix}data:${key}`;
706
+ const data = await client.get(fullKey);
707
+ return data ? JSON.parse(data) : null;
708
+ }
709
+ async set(key, value, ttl) {
710
+ const client = await this._getClient();
711
+ const fullKey = `${this._prefix}data:${key}`;
712
+ if (ttl) {
713
+ const ttlSeconds = Math.ceil(ttl / 1e3);
714
+ await client.set(fullKey, JSON.stringify(value), "EX", String(ttlSeconds));
715
+ } else {
716
+ await client.set(fullKey, JSON.stringify(value));
717
+ }
718
+ }
719
+ async delete(key) {
720
+ const client = await this._getClient();
721
+ const fullKey = `${this._prefix}data:${key}`;
722
+ const result = await client.del(fullKey);
723
+ return result > 0;
724
+ }
725
+ };
726
+ __name(_RedisStorage, "RedisStorage");
727
+ var RedisStorage = _RedisStorage;
728
+ function createRedisStorage(config) {
729
+ return new RedisStorage(config);
730
+ }
731
+ __name(createRedisStorage, "createRedisStorage");
732
+
733
+ // src/storage/MongoStorage.ts
734
+ var _MongoStorage = class _MongoStorage {
735
+ constructor(config) {
736
+ __publicField(this, "_connection");
737
+ __publicField(this, "_transactionCollection");
738
+ __publicField(this, "_dataCollection");
739
+ __publicField(this, "_lockCollection");
740
+ __publicField(this, "_closed", false);
741
+ this._connection = config.connection;
742
+ this._transactionCollection = config.transactionCollection ?? "transactions";
743
+ this._dataCollection = config.dataCollection ?? "transaction_data";
744
+ this._lockCollection = config.lockCollection ?? "transaction_locks";
745
+ }
746
+ // =========================================================================
747
+ // 生命周期 | Lifecycle
748
+ // =========================================================================
749
+ /**
750
+ * @zh 获取集合
751
+ * @en Get collection
752
+ */
753
+ _getCollection(name) {
754
+ if (this._closed) {
755
+ throw new Error("MongoStorage is closed");
756
+ }
757
+ if (!this._connection.isConnected()) {
758
+ throw new Error("MongoDB connection is not connected");
759
+ }
760
+ return this._connection.collection(name);
761
+ }
762
+ /**
763
+ * @zh 关闭存储
764
+ * @en Close storage
765
+ *
766
+ * @zh 不会关闭共享连接,只标记存储为已关闭
767
+ * @en Does not close shared connection, only marks storage as closed
768
+ */
769
+ async close() {
770
+ this._closed = true;
771
+ }
772
+ /**
773
+ * @zh 支持 await using 语法
774
+ * @en Support await using syntax
775
+ */
776
+ async [Symbol.asyncDispose]() {
777
+ await this.close();
778
+ }
779
+ /**
780
+ * @zh 确保索引存在
781
+ * @en Ensure indexes exist
782
+ */
783
+ async ensureIndexes() {
784
+ const txColl = this._getCollection(this._transactionCollection);
785
+ await txColl.createIndex({
786
+ state: 1
787
+ });
788
+ await txColl.createIndex({
789
+ "metadata.serverId": 1
790
+ });
791
+ await txColl.createIndex({
792
+ createdAt: 1
793
+ });
794
+ const lockColl = this._getCollection(this._lockCollection);
795
+ await lockColl.createIndex({
796
+ expireAt: 1
797
+ }, {
798
+ expireAfterSeconds: 0
799
+ });
800
+ const dataColl = this._getCollection(this._dataCollection);
801
+ await dataColl.createIndex({
802
+ expireAt: 1
803
+ }, {
804
+ expireAfterSeconds: 0
805
+ });
806
+ }
807
+ // =========================================================================
808
+ // 分布式锁 | Distributed Lock
809
+ // =========================================================================
810
+ async acquireLock(key, ttl) {
811
+ const coll = this._getCollection(this._lockCollection);
812
+ const token = `${Date.now()}_${Math.random().toString(36).substring(2)}`;
813
+ const expireAt = new Date(Date.now() + ttl);
814
+ try {
815
+ await coll.insertOne({
816
+ _id: key,
817
+ token,
818
+ expireAt
819
+ });
820
+ return token;
821
+ } catch {
822
+ const existing = await coll.findOne({
823
+ _id: key
824
+ });
825
+ if (existing && existing.expireAt < /* @__PURE__ */ new Date()) {
826
+ const result = await coll.updateOne({
827
+ _id: key,
828
+ expireAt: {
829
+ $lt: /* @__PURE__ */ new Date()
830
+ }
831
+ }, {
832
+ $set: {
833
+ token,
834
+ expireAt
835
+ }
836
+ });
837
+ if (result.modifiedCount > 0) {
838
+ return token;
839
+ }
840
+ }
841
+ return null;
842
+ }
843
+ }
844
+ async releaseLock(key, token) {
845
+ const coll = this._getCollection(this._lockCollection);
846
+ const result = await coll.deleteOne({
847
+ _id: key,
848
+ token
849
+ });
850
+ return result.deletedCount > 0;
851
+ }
852
+ // =========================================================================
853
+ // 事务日志 | Transaction Log
854
+ // =========================================================================
855
+ async saveTransaction(tx) {
856
+ const coll = this._getCollection(this._transactionCollection);
857
+ const existing = await coll.findOne({
858
+ _id: tx.id
859
+ });
860
+ if (existing) {
861
+ await coll.updateOne({
862
+ _id: tx.id
863
+ }, {
864
+ $set: {
865
+ ...tx,
866
+ _id: tx.id
867
+ }
868
+ });
869
+ } else {
870
+ await coll.insertOne({
871
+ ...tx,
872
+ _id: tx.id
873
+ });
874
+ }
875
+ }
876
+ async getTransaction(id) {
877
+ const coll = this._getCollection(this._transactionCollection);
878
+ const doc = await coll.findOne({
879
+ _id: id
880
+ });
881
+ if (!doc) return null;
882
+ const { _id, ...tx } = doc;
883
+ return tx;
884
+ }
885
+ async updateTransactionState(id, state) {
886
+ const coll = this._getCollection(this._transactionCollection);
887
+ await coll.updateOne({
888
+ _id: id
889
+ }, {
890
+ $set: {
891
+ state,
892
+ updatedAt: Date.now()
893
+ }
894
+ });
895
+ }
896
+ async updateOperationState(transactionId, operationIndex, state, error) {
897
+ const coll = this._getCollection(this._transactionCollection);
898
+ const update = {
899
+ [`operations.${operationIndex}.state`]: state,
900
+ updatedAt: Date.now()
901
+ };
902
+ if (error) {
903
+ update[`operations.${operationIndex}.error`] = error;
904
+ }
905
+ if (state === "executed") {
906
+ update[`operations.${operationIndex}.executedAt`] = Date.now();
907
+ } else if (state === "compensated") {
908
+ update[`operations.${operationIndex}.compensatedAt`] = Date.now();
909
+ }
910
+ await coll.updateOne({
911
+ _id: transactionId
912
+ }, {
913
+ $set: update
914
+ });
915
+ }
916
+ async getPendingTransactions(serverId) {
917
+ const coll = this._getCollection(this._transactionCollection);
918
+ const filter = {
919
+ state: {
920
+ $in: [
921
+ "pending",
922
+ "executing"
923
+ ]
924
+ }
925
+ };
926
+ if (serverId) {
927
+ filter["metadata.serverId"] = serverId;
928
+ }
929
+ const docs = await coll.find(filter);
930
+ return docs.map(({ _id, ...tx }) => tx);
931
+ }
932
+ async deleteTransaction(id) {
933
+ const coll = this._getCollection(this._transactionCollection);
934
+ await coll.deleteOne({
935
+ _id: id
936
+ });
937
+ }
938
+ // =========================================================================
939
+ // 数据操作 | Data Operations
940
+ // =========================================================================
941
+ async get(key) {
942
+ const coll = this._getCollection(this._dataCollection);
943
+ const doc = await coll.findOne({
944
+ _id: key
945
+ });
946
+ if (!doc) return null;
947
+ if (doc.expireAt && doc.expireAt < /* @__PURE__ */ new Date()) {
948
+ await coll.deleteOne({
949
+ _id: key
950
+ });
951
+ return null;
952
+ }
953
+ return doc.value;
954
+ }
955
+ async set(key, value, ttl) {
956
+ const coll = this._getCollection(this._dataCollection);
957
+ const doc = {
958
+ _id: key,
959
+ value
960
+ };
961
+ if (ttl) {
962
+ doc.expireAt = new Date(Date.now() + ttl);
963
+ }
964
+ const existing = await coll.findOne({
965
+ _id: key
966
+ });
967
+ if (existing) {
968
+ await coll.updateOne({
969
+ _id: key
970
+ }, {
971
+ $set: doc
972
+ });
973
+ } else {
974
+ await coll.insertOne(doc);
975
+ }
976
+ }
977
+ async delete(key) {
978
+ const coll = this._getCollection(this._dataCollection);
979
+ const result = await coll.deleteOne({
980
+ _id: key
981
+ });
982
+ return result.deletedCount > 0;
983
+ }
984
+ };
985
+ __name(_MongoStorage, "MongoStorage");
986
+ var MongoStorage = _MongoStorage;
987
+ function createMongoStorage(connection, options) {
988
+ return new MongoStorage({
989
+ connection,
990
+ ...options
991
+ });
992
+ }
993
+ __name(createMongoStorage, "createMongoStorage");
994
+
995
+ // src/operations/BaseOperation.ts
996
+ var _BaseOperation = class _BaseOperation {
997
+ constructor(data) {
998
+ __publicField(this, "data");
999
+ this.data = data;
1000
+ }
1001
+ /**
1002
+ * @zh 验证前置条件(默认通过)
1003
+ * @en Validate preconditions (passes by default)
1004
+ */
1005
+ async validate(_ctx) {
1006
+ return true;
1007
+ }
1008
+ /**
1009
+ * @zh 创建成功结果
1010
+ * @en Create success result
1011
+ */
1012
+ success(data) {
1013
+ return {
1014
+ success: true,
1015
+ data
1016
+ };
1017
+ }
1018
+ /**
1019
+ * @zh 创建失败结果
1020
+ * @en Create failure result
1021
+ */
1022
+ failure(error, errorCode) {
1023
+ return {
1024
+ success: false,
1025
+ error,
1026
+ errorCode
1027
+ };
1028
+ }
1029
+ };
1030
+ __name(_BaseOperation, "BaseOperation");
1031
+ var BaseOperation = _BaseOperation;
1032
+
1033
+ // src/operations/CurrencyOperation.ts
1034
+ var _CurrencyOperation = class _CurrencyOperation extends BaseOperation {
1035
+ constructor() {
1036
+ super(...arguments);
1037
+ __publicField(this, "name", "currency");
1038
+ __publicField(this, "_provider", null);
1039
+ __publicField(this, "_beforeBalance", 0);
1040
+ }
1041
+ /**
1042
+ * @zh 设置货币数据提供者
1043
+ * @en Set currency data provider
1044
+ */
1045
+ setProvider(provider) {
1046
+ this._provider = provider;
1047
+ return this;
1048
+ }
1049
+ async validate(ctx) {
1050
+ if (this.data.amount <= 0) {
1051
+ return false;
1052
+ }
1053
+ if (this.data.type === "deduct") {
1054
+ const balance = await this._getBalance(ctx);
1055
+ return balance >= this.data.amount;
1056
+ }
1057
+ return true;
1058
+ }
1059
+ async execute(ctx) {
1060
+ const { type, playerId, currency, amount } = this.data;
1061
+ this._beforeBalance = await this._getBalance(ctx);
1062
+ let afterBalance;
1063
+ if (type === "add") {
1064
+ afterBalance = this._beforeBalance + amount;
1065
+ } else {
1066
+ if (this._beforeBalance < amount) {
1067
+ return this.failure("Insufficient balance", "INSUFFICIENT_BALANCE");
1068
+ }
1069
+ afterBalance = this._beforeBalance - amount;
1070
+ }
1071
+ await this._setBalance(ctx, afterBalance);
1072
+ ctx.set(`currency:${playerId}:${currency}:before`, this._beforeBalance);
1073
+ ctx.set(`currency:${playerId}:${currency}:after`, afterBalance);
1074
+ return this.success({
1075
+ beforeBalance: this._beforeBalance,
1076
+ afterBalance
1077
+ });
1078
+ }
1079
+ async compensate(ctx) {
1080
+ await this._setBalance(ctx, this._beforeBalance);
1081
+ }
1082
+ async _getBalance(ctx) {
1083
+ const { playerId, currency } = this.data;
1084
+ if (this._provider) {
1085
+ return this._provider.getBalance(playerId, currency);
1086
+ }
1087
+ if (ctx.storage) {
1088
+ const balance = await ctx.storage.get(`player:${playerId}:currency:${currency}`);
1089
+ return balance ?? 0;
1090
+ }
1091
+ return 0;
1092
+ }
1093
+ async _setBalance(ctx, amount) {
1094
+ const { playerId, currency } = this.data;
1095
+ if (this._provider) {
1096
+ await this._provider.setBalance(playerId, currency, amount);
1097
+ return;
1098
+ }
1099
+ if (ctx.storage) {
1100
+ await ctx.storage.set(`player:${playerId}:currency:${currency}`, amount);
1101
+ }
1102
+ }
1103
+ };
1104
+ __name(_CurrencyOperation, "CurrencyOperation");
1105
+ var CurrencyOperation = _CurrencyOperation;
1106
+ function createCurrencyOperation(data) {
1107
+ return new CurrencyOperation(data);
1108
+ }
1109
+ __name(createCurrencyOperation, "createCurrencyOperation");
1110
+
1111
+ // src/operations/InventoryOperation.ts
1112
+ var _InventoryOperation = class _InventoryOperation extends BaseOperation {
1113
+ constructor() {
1114
+ super(...arguments);
1115
+ __publicField(this, "name", "inventory");
1116
+ __publicField(this, "_provider", null);
1117
+ __publicField(this, "_beforeItem", null);
1118
+ }
1119
+ /**
1120
+ * @zh 设置背包数据提供者
1121
+ * @en Set inventory data provider
1122
+ */
1123
+ setProvider(provider) {
1124
+ this._provider = provider;
1125
+ return this;
1126
+ }
1127
+ async validate(ctx) {
1128
+ const { type, quantity } = this.data;
1129
+ if (quantity <= 0) {
1130
+ return false;
1131
+ }
1132
+ if (type === "remove") {
1133
+ const item = await this._getItem(ctx);
1134
+ return item !== null && item.quantity >= quantity;
1135
+ }
1136
+ if (type === "add" && this._provider?.hasCapacity) {
1137
+ return this._provider.hasCapacity(this.data.playerId, 1);
1138
+ }
1139
+ return true;
1140
+ }
1141
+ async execute(ctx) {
1142
+ const { type, playerId, itemId, quantity, properties } = this.data;
1143
+ this._beforeItem = await this._getItem(ctx);
1144
+ let afterItem = null;
1145
+ switch (type) {
1146
+ case "add": {
1147
+ if (this._beforeItem) {
1148
+ afterItem = {
1149
+ ...this._beforeItem,
1150
+ quantity: this._beforeItem.quantity + quantity
1151
+ };
1152
+ } else {
1153
+ afterItem = {
1154
+ itemId,
1155
+ quantity,
1156
+ properties
1157
+ };
1158
+ }
1159
+ break;
1160
+ }
1161
+ case "remove": {
1162
+ if (!this._beforeItem || this._beforeItem.quantity < quantity) {
1163
+ return this.failure("Insufficient item quantity", "INSUFFICIENT_ITEM");
1164
+ }
1165
+ const newQuantity = this._beforeItem.quantity - quantity;
1166
+ if (newQuantity > 0) {
1167
+ afterItem = {
1168
+ ...this._beforeItem,
1169
+ quantity: newQuantity
1170
+ };
1171
+ } else {
1172
+ afterItem = null;
1173
+ }
1174
+ break;
1175
+ }
1176
+ case "update": {
1177
+ if (!this._beforeItem) {
1178
+ return this.failure("Item not found", "ITEM_NOT_FOUND");
1179
+ }
1180
+ afterItem = {
1181
+ ...this._beforeItem,
1182
+ quantity: quantity > 0 ? quantity : this._beforeItem.quantity,
1183
+ properties: properties ?? this._beforeItem.properties
1184
+ };
1185
+ break;
1186
+ }
1187
+ }
1188
+ await this._setItem(ctx, afterItem);
1189
+ ctx.set(`inventory:${playerId}:${itemId}:before`, this._beforeItem);
1190
+ ctx.set(`inventory:${playerId}:${itemId}:after`, afterItem);
1191
+ return this.success({
1192
+ beforeItem: this._beforeItem ?? void 0,
1193
+ afterItem: afterItem ?? void 0
1194
+ });
1195
+ }
1196
+ async compensate(ctx) {
1197
+ await this._setItem(ctx, this._beforeItem);
1198
+ }
1199
+ async _getItem(ctx) {
1200
+ const { playerId, itemId } = this.data;
1201
+ if (this._provider) {
1202
+ return this._provider.getItem(playerId, itemId);
1203
+ }
1204
+ if (ctx.storage) {
1205
+ return ctx.storage.get(`player:${playerId}:inventory:${itemId}`);
1206
+ }
1207
+ return null;
1208
+ }
1209
+ async _setItem(ctx, item) {
1210
+ const { playerId, itemId } = this.data;
1211
+ if (this._provider) {
1212
+ await this._provider.setItem(playerId, itemId, item);
1213
+ return;
1214
+ }
1215
+ if (ctx.storage) {
1216
+ if (item) {
1217
+ await ctx.storage.set(`player:${playerId}:inventory:${itemId}`, item);
1218
+ } else {
1219
+ await ctx.storage.delete(`player:${playerId}:inventory:${itemId}`);
1220
+ }
1221
+ }
1222
+ }
1223
+ };
1224
+ __name(_InventoryOperation, "InventoryOperation");
1225
+ var InventoryOperation = _InventoryOperation;
1226
+ function createInventoryOperation(data) {
1227
+ return new InventoryOperation(data);
1228
+ }
1229
+ __name(createInventoryOperation, "createInventoryOperation");
1230
+
1231
+ // src/operations/TradeOperation.ts
1232
+ var _TradeOperation = class _TradeOperation extends BaseOperation {
1233
+ constructor() {
1234
+ super(...arguments);
1235
+ __publicField(this, "name", "trade");
1236
+ __publicField(this, "_provider", null);
1237
+ __publicField(this, "_subOperations", []);
1238
+ __publicField(this, "_executedCount", 0);
1239
+ }
1240
+ /**
1241
+ * @zh 设置交易数据提供者
1242
+ * @en Set trade data provider
1243
+ */
1244
+ setProvider(provider) {
1245
+ this._provider = provider;
1246
+ return this;
1247
+ }
1248
+ async validate(ctx) {
1249
+ this._buildSubOperations();
1250
+ for (const op of this._subOperations) {
1251
+ const isValid = await op.validate(ctx);
1252
+ if (!isValid) {
1253
+ return false;
1254
+ }
1255
+ }
1256
+ return true;
1257
+ }
1258
+ async execute(ctx) {
1259
+ this._buildSubOperations();
1260
+ this._executedCount = 0;
1261
+ try {
1262
+ for (const op of this._subOperations) {
1263
+ const result = await op.execute(ctx);
1264
+ if (!result.success) {
1265
+ await this._compensateExecuted(ctx);
1266
+ return this.failure(result.error ?? "Trade operation failed", "TRADE_FAILED");
1267
+ }
1268
+ this._executedCount++;
1269
+ }
1270
+ return this.success({
1271
+ tradeId: this.data.tradeId,
1272
+ completed: true
1273
+ });
1274
+ } catch (error) {
1275
+ await this._compensateExecuted(ctx);
1276
+ const errorMessage = error instanceof Error ? error.message : String(error);
1277
+ return this.failure(errorMessage, "TRADE_ERROR");
1278
+ }
1279
+ }
1280
+ async compensate(ctx) {
1281
+ await this._compensateExecuted(ctx);
1282
+ }
1283
+ _buildSubOperations() {
1284
+ if (this._subOperations.length > 0) return;
1285
+ const { partyA, partyB } = this.data;
1286
+ if (partyA.items) {
1287
+ for (const item of partyA.items) {
1288
+ const removeOp = new InventoryOperation({
1289
+ type: "remove",
1290
+ playerId: partyA.playerId,
1291
+ itemId: item.itemId,
1292
+ quantity: item.quantity,
1293
+ reason: `trade:${this.data.tradeId}:give`
1294
+ });
1295
+ const addOp = new InventoryOperation({
1296
+ type: "add",
1297
+ playerId: partyB.playerId,
1298
+ itemId: item.itemId,
1299
+ quantity: item.quantity,
1300
+ reason: `trade:${this.data.tradeId}:receive`
1301
+ });
1302
+ if (this._provider?.inventoryProvider) {
1303
+ removeOp.setProvider(this._provider.inventoryProvider);
1304
+ addOp.setProvider(this._provider.inventoryProvider);
1305
+ }
1306
+ this._subOperations.push(removeOp, addOp);
1307
+ }
1308
+ }
1309
+ if (partyA.currencies) {
1310
+ for (const curr of partyA.currencies) {
1311
+ const deductOp = new CurrencyOperation({
1312
+ type: "deduct",
1313
+ playerId: partyA.playerId,
1314
+ currency: curr.currency,
1315
+ amount: curr.amount,
1316
+ reason: `trade:${this.data.tradeId}:give`
1317
+ });
1318
+ const addOp = new CurrencyOperation({
1319
+ type: "add",
1320
+ playerId: partyB.playerId,
1321
+ currency: curr.currency,
1322
+ amount: curr.amount,
1323
+ reason: `trade:${this.data.tradeId}:receive`
1324
+ });
1325
+ if (this._provider?.currencyProvider) {
1326
+ deductOp.setProvider(this._provider.currencyProvider);
1327
+ addOp.setProvider(this._provider.currencyProvider);
1328
+ }
1329
+ this._subOperations.push(deductOp, addOp);
1330
+ }
1331
+ }
1332
+ if (partyB.items) {
1333
+ for (const item of partyB.items) {
1334
+ const removeOp = new InventoryOperation({
1335
+ type: "remove",
1336
+ playerId: partyB.playerId,
1337
+ itemId: item.itemId,
1338
+ quantity: item.quantity,
1339
+ reason: `trade:${this.data.tradeId}:give`
1340
+ });
1341
+ const addOp = new InventoryOperation({
1342
+ type: "add",
1343
+ playerId: partyA.playerId,
1344
+ itemId: item.itemId,
1345
+ quantity: item.quantity,
1346
+ reason: `trade:${this.data.tradeId}:receive`
1347
+ });
1348
+ if (this._provider?.inventoryProvider) {
1349
+ removeOp.setProvider(this._provider.inventoryProvider);
1350
+ addOp.setProvider(this._provider.inventoryProvider);
1351
+ }
1352
+ this._subOperations.push(removeOp, addOp);
1353
+ }
1354
+ }
1355
+ if (partyB.currencies) {
1356
+ for (const curr of partyB.currencies) {
1357
+ const deductOp = new CurrencyOperation({
1358
+ type: "deduct",
1359
+ playerId: partyB.playerId,
1360
+ currency: curr.currency,
1361
+ amount: curr.amount,
1362
+ reason: `trade:${this.data.tradeId}:give`
1363
+ });
1364
+ const addOp = new CurrencyOperation({
1365
+ type: "add",
1366
+ playerId: partyA.playerId,
1367
+ currency: curr.currency,
1368
+ amount: curr.amount,
1369
+ reason: `trade:${this.data.tradeId}:receive`
1370
+ });
1371
+ if (this._provider?.currencyProvider) {
1372
+ deductOp.setProvider(this._provider.currencyProvider);
1373
+ addOp.setProvider(this._provider.currencyProvider);
1374
+ }
1375
+ this._subOperations.push(deductOp, addOp);
1376
+ }
1377
+ }
1378
+ }
1379
+ async _compensateExecuted(ctx) {
1380
+ for (let i = this._executedCount - 1; i >= 0; i--) {
1381
+ await this._subOperations[i].compensate(ctx);
1382
+ }
1383
+ }
1384
+ };
1385
+ __name(_TradeOperation, "TradeOperation");
1386
+ var TradeOperation = _TradeOperation;
1387
+ function createTradeOperation(data) {
1388
+ return new TradeOperation(data);
1389
+ }
1390
+ __name(createTradeOperation, "createTradeOperation");
1391
+
1392
+ // src/distributed/SagaOrchestrator.ts
1393
+ function generateSagaId() {
1394
+ return `saga_${Date.now().toString(36)}_${Math.random().toString(36).substring(2, 11)}`;
1395
+ }
1396
+ __name(generateSagaId, "generateSagaId");
1397
+ var _SagaOrchestrator = class _SagaOrchestrator {
1398
+ constructor(config = {}) {
1399
+ __publicField(this, "_storage");
1400
+ __publicField(this, "_timeout");
1401
+ __publicField(this, "_serverId");
1402
+ this._storage = config.storage ?? null;
1403
+ this._timeout = config.timeout ?? 3e4;
1404
+ this._serverId = config.serverId ?? "default";
1405
+ }
1406
+ /**
1407
+ * @zh 执行 Saga
1408
+ * @en Execute Saga
1409
+ */
1410
+ async execute(steps) {
1411
+ const sagaId = generateSagaId();
1412
+ const startTime = Date.now();
1413
+ const completedSteps = [];
1414
+ const sagaLog = {
1415
+ id: sagaId,
1416
+ state: "pending",
1417
+ steps: steps.map((s) => ({
1418
+ name: s.name,
1419
+ serverId: s.serverId,
1420
+ state: "pending"
1421
+ })),
1422
+ createdAt: startTime,
1423
+ updatedAt: startTime,
1424
+ metadata: {
1425
+ orchestratorServerId: this._serverId
1426
+ }
1427
+ };
1428
+ await this._saveSagaLog(sagaLog);
1429
+ try {
1430
+ sagaLog.state = "running";
1431
+ await this._saveSagaLog(sagaLog);
1432
+ for (let i = 0; i < steps.length; i++) {
1433
+ const step = steps[i];
1434
+ if (Date.now() - startTime > this._timeout) {
1435
+ throw new Error("Saga execution timed out");
1436
+ }
1437
+ sagaLog.steps[i].state = "executing";
1438
+ sagaLog.steps[i].startedAt = Date.now();
1439
+ await this._saveSagaLog(sagaLog);
1440
+ const result = await step.execute(step.data);
1441
+ if (!result.success) {
1442
+ sagaLog.steps[i].state = "failed";
1443
+ sagaLog.steps[i].error = result.error;
1444
+ await this._saveSagaLog(sagaLog);
1445
+ throw new Error(result.error ?? `Step ${step.name} failed`);
1446
+ }
1447
+ sagaLog.steps[i].state = "completed";
1448
+ sagaLog.steps[i].completedAt = Date.now();
1449
+ completedSteps.push(step.name);
1450
+ await this._saveSagaLog(sagaLog);
1451
+ }
1452
+ sagaLog.state = "completed";
1453
+ sagaLog.updatedAt = Date.now();
1454
+ await this._saveSagaLog(sagaLog);
1455
+ return {
1456
+ success: true,
1457
+ sagaId,
1458
+ completedSteps,
1459
+ duration: Date.now() - startTime
1460
+ };
1461
+ } catch (error) {
1462
+ const errorMessage = error instanceof Error ? error.message : String(error);
1463
+ const failedStepIndex = completedSteps.length;
1464
+ sagaLog.state = "compensating";
1465
+ await this._saveSagaLog(sagaLog);
1466
+ for (let i = completedSteps.length - 1; i >= 0; i--) {
1467
+ const step = steps[i];
1468
+ sagaLog.steps[i].state = "compensating";
1469
+ await this._saveSagaLog(sagaLog);
1470
+ try {
1471
+ await step.compensate(step.data);
1472
+ sagaLog.steps[i].state = "compensated";
1473
+ } catch (compError) {
1474
+ const compErrorMessage = compError instanceof Error ? compError.message : String(compError);
1475
+ sagaLog.steps[i].state = "failed";
1476
+ sagaLog.steps[i].error = `Compensation failed: ${compErrorMessage}`;
1477
+ }
1478
+ await this._saveSagaLog(sagaLog);
1479
+ }
1480
+ sagaLog.state = "compensated";
1481
+ sagaLog.updatedAt = Date.now();
1482
+ await this._saveSagaLog(sagaLog);
1483
+ return {
1484
+ success: false,
1485
+ sagaId,
1486
+ completedSteps,
1487
+ failedStep: steps[failedStepIndex]?.name,
1488
+ error: errorMessage,
1489
+ duration: Date.now() - startTime
1490
+ };
1491
+ }
1492
+ }
1493
+ /**
1494
+ * @zh 恢复未完成的 Saga
1495
+ * @en Recover pending Sagas
1496
+ */
1497
+ async recover() {
1498
+ if (!this._storage) return 0;
1499
+ const pendingSagas = await this._getPendingSagas();
1500
+ let recoveredCount = 0;
1501
+ for (const saga of pendingSagas) {
1502
+ try {
1503
+ await this._recoverSaga(saga);
1504
+ recoveredCount++;
1505
+ } catch (error) {
1506
+ console.error(`Failed to recover saga ${saga.id}:`, error);
1507
+ }
1508
+ }
1509
+ return recoveredCount;
1510
+ }
1511
+ /**
1512
+ * @zh 获取 Saga 日志
1513
+ * @en Get Saga log
1514
+ */
1515
+ async getSagaLog(sagaId) {
1516
+ if (!this._storage) return null;
1517
+ return this._storage.get(`saga:${sagaId}`);
1518
+ }
1519
+ async _saveSagaLog(log) {
1520
+ if (!this._storage) return;
1521
+ log.updatedAt = Date.now();
1522
+ await this._storage.set(`saga:${log.id}`, log);
1523
+ }
1524
+ async _getPendingSagas() {
1525
+ return [];
1526
+ }
1527
+ async _recoverSaga(saga) {
1528
+ if (saga.state === "running" || saga.state === "compensating") {
1529
+ const completedSteps = saga.steps.filter((s) => s.state === "completed").map((s) => s.name);
1530
+ saga.state = "compensated";
1531
+ saga.updatedAt = Date.now();
1532
+ if (this._storage) {
1533
+ await this._storage.set(`saga:${saga.id}`, saga);
1534
+ }
1535
+ }
1536
+ }
1537
+ };
1538
+ __name(_SagaOrchestrator, "SagaOrchestrator");
1539
+ var SagaOrchestrator = _SagaOrchestrator;
1540
+ function createSagaOrchestrator(config = {}) {
1541
+ return new SagaOrchestrator(config);
1542
+ }
1543
+ __name(createSagaOrchestrator, "createSagaOrchestrator");
1544
+
1545
+ // src/integration/RoomTransactionMixin.ts
1546
+ function withTransactions(Base, config = {}) {
1547
+ var _a;
1548
+ return _a = class extends Base {
1549
+ constructor(...args) {
1550
+ super(...args);
1551
+ __publicField(this, "_transactionManager");
1552
+ this._transactionManager = new TransactionManager({
1553
+ storage: config.storage,
1554
+ defaultTimeout: config.defaultTimeout,
1555
+ serverId: config.serverId
1556
+ });
1557
+ }
1558
+ get transactions() {
1559
+ return this._transactionManager;
1560
+ }
1561
+ beginTransaction(options) {
1562
+ return this._transactionManager.begin(options);
1563
+ }
1564
+ runTransaction(builder, options) {
1565
+ return this._transactionManager.run(builder, options);
1566
+ }
1567
+ }, __name(_a, "TransactionRoom"), _a;
1568
+ }
1569
+ __name(withTransactions, "withTransactions");
1570
+ var _TransactionRoom = class _TransactionRoom {
1571
+ constructor(config = {}) {
1572
+ __publicField(this, "_transactionManager");
1573
+ this._transactionManager = new TransactionManager({
1574
+ storage: config.storage,
1575
+ defaultTimeout: config.defaultTimeout,
1576
+ serverId: config.serverId
1577
+ });
1578
+ }
1579
+ get transactions() {
1580
+ return this._transactionManager;
1581
+ }
1582
+ beginTransaction(options) {
1583
+ return this._transactionManager.begin(options);
1584
+ }
1585
+ runTransaction(builder, options) {
1586
+ return this._transactionManager.run(builder, options);
1587
+ }
1588
+ };
1589
+ __name(_TransactionRoom, "TransactionRoom");
1590
+ var TransactionRoom = _TransactionRoom;
1591
+
1592
+ // src/tokens.ts
1593
+ import { createServiceToken } from "@esengine/ecs-framework";
1594
+ var TransactionManagerToken = createServiceToken("transactionManager");
1595
+ var TransactionStorageToken = createServiceToken("transactionStorage");
1596
+ export {
1597
+ BaseOperation,
1598
+ CurrencyOperation,
1599
+ InventoryOperation,
1600
+ MemoryStorage,
1601
+ MongoStorage,
1602
+ RedisStorage,
1603
+ SagaOrchestrator,
1604
+ TradeOperation,
1605
+ TransactionContext,
1606
+ TransactionManager,
1607
+ TransactionManagerToken,
1608
+ TransactionRoom,
1609
+ TransactionStorageToken,
1610
+ createCurrencyOperation,
1611
+ createInventoryOperation,
1612
+ createMemoryStorage,
1613
+ createMongoStorage,
1614
+ createRedisStorage,
1615
+ createSagaOrchestrator,
1616
+ createTradeOperation,
1617
+ createTransactionContext,
1618
+ createTransactionManager,
1619
+ withTransactions
1620
+ };
1621
+ //# sourceMappingURL=index.js.map