@sfmc-bds/db-server 0.1.0 → 0.2.0-beta.0

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 (112) hide show
  1. package/README.md +1 -1
  2. package/dist/db-tables.js +16 -17
  3. package/dist/db-tables.js.map +7 -1
  4. package/dist/domain/bridge.js +54 -77
  5. package/dist/domain/bridge.js.map +7 -1
  6. package/dist/domain/economy.d.ts +83 -2
  7. package/dist/domain/economy.d.ts.map +1 -1
  8. package/dist/domain/economy.js +292 -281
  9. package/dist/domain/economy.js.map +7 -1
  10. package/dist/domain/schema.js +480 -426
  11. package/dist/domain/schema.js.map +7 -1
  12. package/dist/domain/transaction.js +32 -80
  13. package/dist/domain/transaction.js.map +7 -1
  14. package/dist/env.d.ts.map +1 -1
  15. package/dist/env.js +85 -84
  16. package/dist/env.js.map +7 -1
  17. package/dist/index.d.ts +1 -1
  18. package/dist/index.d.ts.map +1 -1
  19. package/dist/index.js +218 -240
  20. package/dist/index.js.map +7 -1
  21. package/dist/lib/http.js +30 -39
  22. package/dist/lib/http.js.map +7 -1
  23. package/dist/lib/idempotency-store.js +34 -47
  24. package/dist/lib/idempotency-store.js.map +7 -1
  25. package/dist/lib/idempotency.js +8 -13
  26. package/dist/lib/idempotency.js.map +7 -1
  27. package/dist/lib/identifiers.js +18 -14
  28. package/dist/lib/identifiers.js.map +7 -1
  29. package/dist/lib/log.d.ts +3 -6
  30. package/dist/lib/log.d.ts.map +1 -1
  31. package/dist/lib/log.js +13 -14
  32. package/dist/lib/log.js.map +7 -1
  33. package/dist/lib/module-state.d.ts.map +1 -1
  34. package/dist/lib/module-state.js +32 -21
  35. package/dist/lib/module-state.js.map +7 -1
  36. package/dist/lib/order-by.d.ts +17 -0
  37. package/dist/lib/order-by.d.ts.map +1 -0
  38. package/dist/lib/order-by.js +24 -0
  39. package/dist/lib/order-by.js.map +7 -0
  40. package/dist/lib/runtime.js +16 -31
  41. package/dist/lib/runtime.js.map +7 -1
  42. package/dist/lib/sql-helpers.js +9 -27
  43. package/dist/lib/sql-helpers.js.map +7 -1
  44. package/dist/lib/sqlite.js +40 -52
  45. package/dist/lib/sqlite.js.map +7 -1
  46. package/dist/manifest-loader.d.ts.map +1 -1
  47. package/dist/manifest-loader.js +154 -195
  48. package/dist/manifest-loader.js.map +7 -1
  49. package/dist/manifest.d.ts +3 -7
  50. package/dist/manifest.d.ts.map +1 -1
  51. package/dist/manifest.js +76 -115
  52. package/dist/manifest.js.map +7 -1
  53. package/dist/module-auth.d.ts +17 -6
  54. package/dist/module-auth.d.ts.map +1 -1
  55. package/dist/module-auth.js +93 -107
  56. package/dist/module-auth.js.map +7 -1
  57. package/dist/module-runtime-sync.d.ts +37 -0
  58. package/dist/module-runtime-sync.d.ts.map +1 -0
  59. package/dist/module-runtime-sync.js +54 -0
  60. package/dist/module-runtime-sync.js.map +7 -0
  61. package/dist/permission-gate.d.ts.map +1 -1
  62. package/dist/permission-gate.js +55 -86
  63. package/dist/permission-gate.js.map +7 -1
  64. package/dist/routes/_shared.d.ts +7 -2
  65. package/dist/routes/_shared.d.ts.map +1 -1
  66. package/dist/routes/_shared.js +16 -25
  67. package/dist/routes/_shared.js.map +7 -1
  68. package/dist/routes/config.d.ts +11 -1
  69. package/dist/routes/config.d.ts.map +1 -1
  70. package/dist/routes/config.js +167 -213
  71. package/dist/routes/config.js.map +7 -1
  72. package/dist/routes/db-routes.d.ts +6 -10
  73. package/dist/routes/db-routes.d.ts.map +1 -1
  74. package/dist/routes/db-routes.js +198 -172
  75. package/dist/routes/db-routes.js.map +7 -1
  76. package/dist/routes/health.js +15 -20
  77. package/dist/routes/health.js.map +7 -1
  78. package/dist/routes/messages.d.ts.map +1 -1
  79. package/dist/routes/messages.js +56 -63
  80. package/dist/routes/messages.js.map +7 -1
  81. package/dist/routes/module-config-routes.d.ts.map +1 -1
  82. package/dist/routes/module-config-routes.js +92 -111
  83. package/dist/routes/module-config-routes.js.map +7 -1
  84. package/dist/routes/modules.d.ts.map +1 -1
  85. package/dist/routes/modules.js +82 -88
  86. package/dist/routes/modules.js.map +7 -1
  87. package/dist/routes/service-routes.d.ts.map +1 -1
  88. package/dist/routes/service-routes.js +54 -61
  89. package/dist/routes/service-routes.js.map +7 -1
  90. package/dist/runtime.test.js +248 -49
  91. package/dist/runtime.test.js.map +7 -1
  92. package/dist/schema-registry.d.ts.map +1 -1
  93. package/dist/schema-registry.js +119 -160
  94. package/dist/schema-registry.js.map +7 -1
  95. package/dist/server.js +69 -72
  96. package/dist/server.js.map +7 -1
  97. package/dist/service-registry.d.ts.map +1 -1
  98. package/dist/service-registry.js +60 -67
  99. package/dist/service-registry.js.map +7 -1
  100. package/dist/services/builtin-handlers.d.ts +4 -0
  101. package/dist/services/builtin-handlers.d.ts.map +1 -1
  102. package/dist/services/builtin-handlers.js +34 -20
  103. package/dist/services/builtin-handlers.js.map +7 -1
  104. package/dist/services/economy-handlers.js +143 -132
  105. package/dist/services/economy-handlers.js.map +7 -1
  106. package/dist/tx-runner.d.ts +35 -11
  107. package/dist/tx-runner.d.ts.map +1 -1
  108. package/dist/tx-runner.js +293 -250
  109. package/dist/tx-runner.js.map +7 -1
  110. package/dist/where.js +71 -94
  111. package/dist/where.js.map +7 -1
  112. package/package.json +4 -4
@@ -1,14 +1,3 @@
1
- /**
2
- * domain/economy.ts — 经济系统业务核心
3
- *
4
- * 提供账户 upsert、通用转账、日常任务奖励与月度统计。
5
- * land / chat / gui 等通过 service(economy.account.*) 调用本文件。
6
- *
7
- * 事务约定(方案 A):
8
- * - applyEconomySteps / submitDailyTaskSteps — 无 BEGIN/COMMIT,可嵌进外层 db.tx
9
- * - applyEconomyTransaction / submitDailyTaskTx — 独立调用时自己开事务;
10
- * alreadyInTx=true 时只跑 steps
11
- */
12
1
  import { isValidIdempotencyKey } from "../lib/idempotency.js";
13
2
  import { sql } from "../lib/sql-helpers.js";
14
3
  const TABLE_ACCOUNTS = "sfmc_economy_accounts";
@@ -17,305 +6,327 @@ const TABLE_IDEMPOTENCY = "sfmc_economy_idempotency";
17
6
  const TABLE_DAILY = "sfmc_economy_daily_tasks";
18
7
  const TABLE_STATS = "sfmc_economy_stats";
19
8
  function newTransactionId(now) {
20
- return `E${now.toString(36)}${Math.random().toString(36).slice(2, 8)}`;
9
+ return `E${now.toString(36)}${Math.random().toString(36).slice(2, 8)}`;
21
10
  }
22
- export function economyResult(row) {
23
- if (!row)
24
- return undefined;
25
- return {
26
- playerId: row.player_id,
27
- playerName: row.player_name_snapshot,
28
- balance: row.balance,
29
- version: row.version,
30
- };
11
+ function economyResult(row) {
12
+ if (!row) return void 0;
13
+ return {
14
+ playerId: row.player_id,
15
+ playerName: row.player_name_snapshot,
16
+ balance: row.balance,
17
+ version: row.version
18
+ };
31
19
  }
32
- /** 确保账户存在并返回最新行 */
33
- export function ensureEconomyAccount(query, playerId, playerName) {
34
- const now = Date.now();
35
- query(sql(`INSERT INTO ${TABLE_ACCOUNTS} (player_id, player_name_snapshot, balance, version, created_at, updated_at)
20
+ function ensureEconomyAccount(query, playerId, playerName) {
21
+ const now = Date.now();
22
+ query(
23
+ sql(
24
+ `INSERT INTO ${TABLE_ACCOUNTS} (player_id, player_name_snapshot, balance, version, created_at, updated_at)
36
25
  VALUES (?, ?, 0, 1, ?, ?)
37
26
  ON CONFLICT(player_id) DO UPDATE SET
38
27
  player_name_snapshot = excluded.player_name_snapshot,
39
- updated_at = excluded.updated_at`, [String(playerId), String(playerName), now, now]));
40
- const rows = query(sql(`SELECT * FROM ${TABLE_ACCOUNTS} WHERE player_id = ?`, [String(playerId)]));
41
- if (Array.isArray(rows)) {
42
- return rows[0];
43
- }
44
- return undefined;
28
+ updated_at = excluded.updated_at`,
29
+ [String(playerId), String(playerName), now, now]
30
+ )
31
+ );
32
+ const rows = query(sql(`SELECT * FROM ${TABLE_ACCOUNTS} WHERE player_id = ?`, [String(playerId)]));
33
+ if (Array.isArray(rows)) {
34
+ return rows[0];
35
+ }
36
+ return void 0;
45
37
  }
46
- /** 读取(必要时创建)账户业务视图 */
47
- export function getEconomyAccount(query, playerId, playerName = "") {
48
- const row = ensureEconomyAccount(query, playerId, playerName);
49
- return economyResult(row) ?? null;
38
+ function getEconomyAccount(query, playerId, playerName = "") {
39
+ const row = ensureEconomyAccount(query, playerId, playerName);
40
+ return economyResult(row) ?? null;
50
41
  }
51
- /**
52
- * 经济变更核心(无 BEGIN/COMMIT)
53
- * debit( source)或纯 credit(仅 target)均可成功。
54
- */
55
- export function applyEconomySteps(query, data) {
56
- const amount = Number(data.amount);
57
- const actorId = String(data.actor_id ?? data.actorId ?? "").trim();
58
- if (!actorId || !Number.isSafeInteger(amount) || amount <= 0) {
59
- return { ok: false, error: "invalid_amount", status: 400 };
60
- }
61
- const sourceId = data.source_player_id
62
- ? String(data.source_player_id)
63
- : data.sourcePlayerId
64
- ? String(data.sourcePlayerId)
65
- : null;
66
- const targetId = data.target_player_id
67
- ? String(data.target_player_id)
68
- : data.targetPlayerId
69
- ? String(data.targetPlayerId)
70
- : null;
71
- const idempotencyKey = String(data.idempotency_key ?? data.idempotencyKey ?? "").trim();
72
- if (idempotencyKey && !isValidIdempotencyKey(idempotencyKey)) {
73
- return { ok: false, error: "invalid_idempotency_key", status: 400 };
74
- }
75
- if (!sourceId && !targetId) {
76
- return { ok: false, error: "missing_account", status: 400 };
77
- }
78
- if (sourceId && sourceId !== actorId) {
79
- return { ok: false, error: "forbidden_source", status: 403 };
80
- }
81
- if (sourceId && targetId && sourceId === targetId) {
82
- return { ok: false, error: "same_account", status: 400 };
83
- }
84
- // 幂等回放
85
- if (idempotencyKey) {
86
- const rows = query(sql(`SELECT response_json FROM ${TABLE_IDEMPOTENCY}
87
- WHERE actor_id = ? AND idempotency_key = ?`, [actorId, idempotencyKey]));
88
- const previous = Array.isArray(rows) ? rows[0] : undefined;
89
- if (previous) {
90
- return { ...JSON.parse(previous.response_json), replayed: true };
91
- }
42
+ function applyEconomySteps(query, data) {
43
+ const amount = Number(data.amount);
44
+ const actorId = String(data.actor_id ?? data.actorId ?? "").trim();
45
+ if (!actorId || !Number.isSafeInteger(amount) || amount <= 0) {
46
+ return { ok: false, error: "invalid_amount", status: 400 };
47
+ }
48
+ const sourceId = data.source_player_id ? String(data.source_player_id) : data.sourcePlayerId ? String(data.sourcePlayerId) : null;
49
+ const targetId = data.target_player_id ? String(data.target_player_id) : data.targetPlayerId ? String(data.targetPlayerId) : null;
50
+ const idempotencyKey = String(data.idempotency_key ?? data.idempotencyKey ?? "").trim();
51
+ if (idempotencyKey && !isValidIdempotencyKey(idempotencyKey)) {
52
+ return { ok: false, error: "invalid_idempotency_key", status: 400 };
53
+ }
54
+ if (!sourceId && !targetId) {
55
+ return { ok: false, error: "missing_account", status: 400 };
56
+ }
57
+ if (sourceId && sourceId !== actorId) {
58
+ return { ok: false, error: "forbidden_source", status: 403 };
59
+ }
60
+ if (sourceId && targetId && sourceId === targetId) {
61
+ return { ok: false, error: "same_account", status: 400 };
62
+ }
63
+ if (idempotencyKey) {
64
+ const rows = query(
65
+ sql(
66
+ `SELECT response_json FROM ${TABLE_IDEMPOTENCY}
67
+ WHERE actor_id = ? AND idempotency_key = ?`,
68
+ [actorId, idempotencyKey]
69
+ )
70
+ );
71
+ const previous = Array.isArray(rows) ? rows[0] : void 0;
72
+ if (previous) {
73
+ return { ...JSON.parse(previous.response_json), replayed: true };
92
74
  }
93
- const source = sourceId ? ensureEconomyAccount(query, sourceId, String(data.sourcePlayerName ?? "")) : null;
94
- const target = targetId ? ensureEconomyAccount(query, targetId, String(data.targetPlayerName ?? "")) : null;
95
- if (sourceId && !source) {
96
- return { ok: false, error: "source_not_found", status: 404 };
97
- }
98
- if (targetId && !target) {
99
- return { ok: false, error: "target_not_found", status: 404 };
100
- }
101
- if (source && source.balance < amount) {
102
- return { ok: false, error: "insufficient_funds", balance: source.balance, status: 409 };
103
- }
104
- const now = Date.now();
105
- if (source) {
106
- query(sql(`UPDATE ${TABLE_ACCOUNTS}
75
+ }
76
+ const source = sourceId ? ensureEconomyAccount(query, sourceId, String(data.sourcePlayerName ?? "")) : null;
77
+ const target = targetId ? ensureEconomyAccount(query, targetId, String(data.targetPlayerName ?? "")) : null;
78
+ if (sourceId && !source) {
79
+ return { ok: false, error: "source_not_found", status: 404 };
80
+ }
81
+ if (targetId && !target) {
82
+ return { ok: false, error: "target_not_found", status: 404 };
83
+ }
84
+ if (source && source.balance < amount) {
85
+ return { ok: false, error: "insufficient_funds", balance: source.balance, status: 409 };
86
+ }
87
+ const now = Date.now();
88
+ if (source) {
89
+ query(
90
+ sql(
91
+ `UPDATE ${TABLE_ACCOUNTS}
107
92
  SET balance = balance - ?, version = version + 1, updated_at = ?
108
- WHERE player_id = ? AND balance >= ?`, [amount, now, source.player_id, amount]));
109
- }
110
- if (target) {
111
- query(sql(`UPDATE ${TABLE_ACCOUNTS}
93
+ WHERE player_id = ? AND balance >= ?`,
94
+ [amount, now, source.player_id, amount]
95
+ )
96
+ );
97
+ }
98
+ if (target) {
99
+ query(
100
+ sql(
101
+ `UPDATE ${TABLE_ACCOUNTS}
112
102
  SET balance = balance + ?, version = version + 1, updated_at = ?
113
- WHERE player_id = ?`, [amount, now, target.player_id]));
114
- }
115
- const transactionType = String(data.transaction_type ?? data.type ?? "adjustment");
116
- const referenceType = String(data.reference_type ?? data.referenceType ?? "");
117
- const referenceId = String(data.reference_id ?? data.referenceId ?? "");
118
- const reason = String(data.reason ?? data.reasonAlias ?? "");
119
- const id = newTransactionId(now);
120
- if (source) {
121
- query(sql(`INSERT INTO ${TABLE_TRANSACTIONS}
103
+ WHERE player_id = ?`,
104
+ [amount, now, target.player_id]
105
+ )
106
+ );
107
+ }
108
+ const transactionType = String(data.transaction_type ?? data.type ?? "adjustment");
109
+ const referenceType = String(data.reference_type ?? data.referenceType ?? "");
110
+ const referenceId = String(data.reference_id ?? data.referenceId ?? "");
111
+ const reason = String(data.reason ?? data.reasonAlias ?? "");
112
+ const id = newTransactionId(now);
113
+ if (source) {
114
+ query(
115
+ sql(
116
+ `INSERT INTO ${TABLE_TRANSACTIONS}
122
117
  (id, transaction_type, actor_id, source_player_id, target_player_id,
123
118
  amount, balance_before, balance_after,
124
119
  reference_type, reference_id, reason, created_at)
125
- VALUES (?, ?, ?, ?, NULL, ?, ?, ?, ?, ?, ?, ?)`, [
126
- id + "-dr",
127
- transactionType + ".dr",
128
- actorId,
129
- sourceId,
130
- amount,
131
- source.balance,
132
- source.balance - amount,
133
- referenceType,
134
- referenceId,
135
- reason,
136
- now,
137
- ]));
138
- }
139
- if (target) {
140
- query(sql(`INSERT INTO ${TABLE_TRANSACTIONS}
120
+ VALUES (?, ?, ?, ?, NULL, ?, ?, ?, ?, ?, ?, ?)`,
121
+ [
122
+ id + "-dr",
123
+ transactionType + ".dr",
124
+ actorId,
125
+ sourceId,
126
+ amount,
127
+ source.balance,
128
+ source.balance - amount,
129
+ referenceType,
130
+ referenceId,
131
+ reason,
132
+ now
133
+ ]
134
+ )
135
+ );
136
+ }
137
+ if (target) {
138
+ query(
139
+ sql(
140
+ `INSERT INTO ${TABLE_TRANSACTIONS}
141
141
  (id, transaction_type, actor_id, source_player_id, target_player_id,
142
142
  amount, balance_before, balance_after,
143
143
  reference_type, reference_id, reason, created_at)
144
- VALUES (?, ?, ?, NULL, ?, ?, ?, ?, ?, ?, ?, ?)`, [
145
- id + "-cr",
146
- transactionType + ".cr",
147
- actorId,
148
- targetId,
149
- amount,
150
- target.balance,
151
- target.balance + amount,
152
- referenceType,
153
- referenceId,
154
- reason,
155
- now,
156
- ]));
157
- }
158
- const sourceView = source ? economyResult(ensureEconomyAccount(query, source.player_id, "")) : undefined;
159
- const targetView = target ? economyResult(ensureEconomyAccount(query, target.player_id, "")) : undefined;
160
- const response = {
161
- ok: true,
162
- transactionId: id,
163
- source: sourceView
164
- ? { ...sourceView, balanceBefore: source.balance, balanceAfter: source.balance - amount }
165
- : null,
166
- target: targetView
167
- ? { ...targetView, balanceBefore: target.balance, balanceAfter: target.balance + amount }
168
- : null,
169
- };
170
- if (idempotencyKey) {
171
- query(sql(`INSERT INTO ${TABLE_IDEMPOTENCY}
144
+ VALUES (?, ?, ?, NULL, ?, ?, ?, ?, ?, ?, ?, ?)`,
145
+ [
146
+ id + "-cr",
147
+ transactionType + ".cr",
148
+ actorId,
149
+ targetId,
150
+ amount,
151
+ target.balance,
152
+ target.balance + amount,
153
+ referenceType,
154
+ referenceId,
155
+ reason,
156
+ now
157
+ ]
158
+ )
159
+ );
160
+ }
161
+ const sourceView = source ? economyResult(ensureEconomyAccount(query, source.player_id, "")) : void 0;
162
+ const targetView = target ? economyResult(ensureEconomyAccount(query, target.player_id, "")) : void 0;
163
+ const response = {
164
+ ok: true,
165
+ transactionId: id,
166
+ source: sourceView ? { ...sourceView, balanceBefore: source.balance, balanceAfter: source.balance - amount } : null,
167
+ target: targetView ? { ...targetView, balanceBefore: target.balance, balanceAfter: target.balance + amount } : null
168
+ };
169
+ if (idempotencyKey) {
170
+ query(
171
+ sql(
172
+ `INSERT INTO ${TABLE_IDEMPOTENCY}
172
173
  (actor_id, idempotency_key, transaction_id, response_json, created_at)
173
- VALUES (?, ?, ?, ?, ?)`, [actorId, idempotencyKey, id, JSON.stringify(response), now]));
174
- }
175
- return response;
174
+ VALUES (?, ?, ?, ?, ?)`,
175
+ [actorId, idempotencyKey, id, JSON.stringify(response), now]
176
+ )
177
+ );
178
+ }
179
+ return response;
176
180
  }
177
- /**
178
- * 独立经济事务包装。alreadyInTx=true 时复用外层事务(不再 BEGIN)。
179
- */
180
- export function applyEconomyTransaction(query, db, data, opts) {
181
- if (opts?.alreadyInTx) {
182
- return applyEconomySteps(query, data);
181
+ function applyEconomyTransaction(query, db, data, opts) {
182
+ if (opts?.alreadyInTx) {
183
+ return applyEconomySteps(query, data);
184
+ }
185
+ db.exec("BEGIN IMMEDIATE");
186
+ try {
187
+ const result = applyEconomySteps(query, data);
188
+ if (!result.ok) {
189
+ db.exec("ROLLBACK");
190
+ return result;
183
191
  }
184
- db.exec("BEGIN IMMEDIATE");
192
+ db.exec("COMMIT");
193
+ return result;
194
+ } catch (error) {
185
195
  try {
186
- const result = applyEconomySteps(query, data);
187
- if (!result.ok) {
188
- db.exec("ROLLBACK");
189
- return result;
190
- }
191
- db.exec("COMMIT");
192
- return result;
193
- }
194
- catch (error) {
195
- try {
196
- db.exec("ROLLBACK");
197
- }
198
- catch {
199
- /* ignore */
200
- }
201
- return {
202
- ok: false,
203
- error: error.message || "economy_transaction_failed",
204
- status: 500,
205
- };
196
+ db.exec("ROLLBACK");
197
+ } catch {
206
198
  }
199
+ return {
200
+ ok: false,
201
+ error: error.message || "economy_transaction_failed",
202
+ status: 500
203
+ };
204
+ }
207
205
  }
208
- /** 列出日常任务(默认仅 active 且未过期) */
209
- export function listDailyTasks(query, filter) {
210
- const status = filter?.status ?? "active";
211
- const now = Date.now();
212
- if (filter?.includeExpired) {
213
- const rows = query(sql(`SELECT * FROM ${TABLE_DAILY} WHERE status = ?`, [status]));
214
- return Array.isArray(rows) ? rows : [];
215
- }
216
- const rows = query(sql(`SELECT * FROM ${TABLE_DAILY} WHERE status = ? AND expires_at > ?`, [status, now]));
217
- return Array.isArray(rows) ? rows : [];
206
+ function listDailyTasks(query, filter) {
207
+ const status = filter?.status ?? "active";
208
+ const now = Date.now();
209
+ if (filter?.includeExpired) {
210
+ const rows2 = query(sql(`SELECT * FROM ${TABLE_DAILY} WHERE status = ?`, [status]));
211
+ return Array.isArray(rows2) ? rows2 : [];
212
+ }
213
+ const rows = query(sql(`SELECT * FROM ${TABLE_DAILY} WHERE status = ? AND expires_at > ?`, [status, now]));
214
+ return Array.isArray(rows) ? rows : [];
218
215
  }
219
- /** 日常任务提交核心( BEGIN) */
220
- export function submitDailyTaskSteps(query, data) {
221
- const actorId = data.actorId;
222
- const taskId = data.taskId;
223
- const quantity = data.quantity;
224
- const rows = query(sql(`SELECT * FROM ${TABLE_DAILY}
225
- WHERE id = ? AND status = 'active' AND expires_at > ?`, [taskId, Date.now()]));
226
- const task = rows[0];
227
- if (!task) {
228
- return { ok: false, error: "task_not_found_or_expired", status: 404 };
229
- }
230
- const targetQty = Number(task.target_qty ?? 0);
231
- const filledQty = Number(task.filled_qty ?? 0);
232
- const remaining = targetQty - filledQty;
233
- if (remaining < quantity) {
234
- return { ok: false, error: "task_quota_exceeded", status: 409, extra: { remaining } };
235
- }
236
- const reward = Number(task.unit_reward) * quantity;
237
- query(sql(`UPDATE ${TABLE_DAILY} SET filled_qty = filled_qty + ? WHERE id = ?`, [quantity, taskId]));
238
- const result = applyEconomySteps(query, {
239
- actor_id: actorId,
240
- transaction_type: "daily_task.reward",
241
- target_player_id: actorId,
242
- amount: reward,
243
- reference_type: "daily_task",
244
- reference_id: taskId,
245
- reason: `提交任务 ${task.item_type}*${quantity}`,
246
- });
247
- if (!result.ok) {
248
- return { ok: false, error: result.error, status: result.status };
216
+ function submitDailyTaskSteps(query, data) {
217
+ const actorId = data.actorId;
218
+ const taskId = data.taskId;
219
+ const quantity = data.quantity;
220
+ const rows = query(
221
+ sql(
222
+ `SELECT * FROM ${TABLE_DAILY}
223
+ WHERE id = ? AND status = 'active' AND expires_at > ?`,
224
+ [taskId, Date.now()]
225
+ )
226
+ );
227
+ const task = rows[0];
228
+ if (!task) {
229
+ return { ok: false, error: "task_not_found_or_expired", status: 404 };
230
+ }
231
+ const targetQty = Number(task.target_qty ?? 0);
232
+ const filledQty = Number(task.filled_qty ?? 0);
233
+ const remaining = targetQty - filledQty;
234
+ if (remaining < quantity) {
235
+ return { ok: false, error: "task_quota_exceeded", status: 409, extra: { remaining } };
236
+ }
237
+ const reward = Number(task.unit_reward) * quantity;
238
+ query(sql(`UPDATE ${TABLE_DAILY} SET filled_qty = filled_qty + ? WHERE id = ?`, [quantity, taskId]));
239
+ const result = applyEconomySteps(query, {
240
+ actor_id: actorId,
241
+ transaction_type: "daily_task.reward",
242
+ target_player_id: actorId,
243
+ amount: reward,
244
+ reference_type: "daily_task",
245
+ reference_id: taskId,
246
+ reason: `\u63D0\u4EA4\u4EFB\u52A1 ${task.item_type}*${quantity}`
247
+ });
248
+ if (!result.ok) {
249
+ return { ok: false, error: result.error, status: result.status };
250
+ }
251
+ const tgtFinal = economyResult(ensureEconomyAccount(query, actorId, ""));
252
+ return {
253
+ ok: true,
254
+ data: {
255
+ transactionId: result.transactionId,
256
+ reward,
257
+ balance: tgtFinal?.balance ?? 0,
258
+ balanceVersion: tgtFinal?.version ?? 0
249
259
  }
250
- const tgtFinal = economyResult(ensureEconomyAccount(query, actorId, ""));
251
- return {
252
- ok: true,
253
- data: {
254
- transactionId: result.transactionId,
255
- reward,
256
- balance: tgtFinal?.balance ?? 0,
257
- balanceVersion: tgtFinal?.version ?? 0,
258
- },
259
- };
260
+ };
260
261
  }
261
- /** 日常任务提交包装(可独立事务或嵌入外层 tx) */
262
- export function submitDailyTaskTx(query, db, data, opts) {
263
- if (opts?.alreadyInTx) {
264
- return submitDailyTaskSteps(query, data);
262
+ function submitDailyTaskTx(query, db, data, opts) {
263
+ if (opts?.alreadyInTx) {
264
+ return submitDailyTaskSteps(query, data);
265
+ }
266
+ db.exec("BEGIN IMMEDIATE");
267
+ try {
268
+ const result = submitDailyTaskSteps(query, data);
269
+ if (!result.ok) {
270
+ db.exec("ROLLBACK");
271
+ return result;
265
272
  }
266
- db.exec("BEGIN IMMEDIATE");
273
+ db.exec("COMMIT");
274
+ return result;
275
+ } catch (error) {
267
276
  try {
268
- const result = submitDailyTaskSteps(query, data);
269
- if (!result.ok) {
270
- db.exec("ROLLBACK");
271
- return result;
272
- }
273
- db.exec("COMMIT");
274
- return result;
275
- }
276
- catch (error) {
277
- try {
278
- db.exec("ROLLBACK");
279
- }
280
- catch {
281
- /* ignore */
282
- }
283
- return {
284
- ok: false,
285
- error: error.message || "task_submit_failed",
286
- status: 500,
287
- };
277
+ db.exec("ROLLBACK");
278
+ } catch {
288
279
  }
280
+ return {
281
+ ok: false,
282
+ error: error.message || "task_submit_failed",
283
+ status: 500
284
+ };
285
+ }
289
286
  }
290
- /** 月度/全局经济白皮书(优先读 sfmc_economy_stats,否则现场聚合) */
291
- export function monthlyEconomyStats(query) {
292
- const now = new Date();
293
- const id = `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, "0")}`;
294
- const cached = query(sql(`SELECT * FROM ${TABLE_STATS} WHERE id = ? OR id = ?`, ["global", id]));
295
- if (Array.isArray(cached) && cached.length > 0) {
296
- const row = cached[0];
297
- return {
298
- id: String(row.id ?? id),
299
- total_issued: Number(row.total_issued ?? 0),
300
- total_destroyed: Number(row.total_destroyed ?? 0),
301
- total_supply: Number(row.total_supply ?? 0),
302
- active_accounts: Number(row.active_accounts ?? 0),
303
- };
304
- }
305
- const supplyRows = query(sql(`SELECT COALESCE(SUM(balance),0) AS total_supply, COUNT(*) AS active_accounts FROM ${TABLE_ACCOUNTS}`));
306
- const supply = Array.isArray(supplyRows) ? supplyRows[0] : {};
307
- const issuedRows = query(sql(`SELECT COALESCE(SUM(amount),0) AS total FROM ${TABLE_TRANSACTIONS} WHERE target_player_id IS NOT NULL`));
308
- const destroyedRows = query(sql(`SELECT COALESCE(SUM(amount),0) AS total FROM ${TABLE_TRANSACTIONS} WHERE source_player_id IS NOT NULL`));
309
- const issued = Array.isArray(issuedRows) ? Number(issuedRows[0].total ?? 0) : 0;
310
- const destroyed = Array.isArray(destroyedRows)
311
- ? Number(destroyedRows[0].total ?? 0)
312
- : 0;
287
+ function monthlyEconomyStats(query) {
288
+ const now = /* @__PURE__ */ new Date();
289
+ const id = `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, "0")}`;
290
+ const cached = query(sql(`SELECT * FROM ${TABLE_STATS} WHERE id = ? OR id = ?`, ["global", id]));
291
+ if (Array.isArray(cached) && cached.length > 0) {
292
+ const row = cached[0];
313
293
  return {
314
- id,
315
- total_issued: issued,
316
- total_destroyed: destroyed,
317
- total_supply: Number(supply.total_supply ?? 0),
318
- active_accounts: Number(supply.active_accounts ?? 0),
294
+ id: String(row.id ?? id),
295
+ total_issued: Number(row.total_issued ?? 0),
296
+ total_destroyed: Number(row.total_destroyed ?? 0),
297
+ total_supply: Number(row.total_supply ?? 0),
298
+ active_accounts: Number(row.active_accounts ?? 0)
319
299
  };
300
+ }
301
+ const supplyRows = query(
302
+ sql(`SELECT COALESCE(SUM(balance),0) AS total_supply, COUNT(*) AS active_accounts FROM ${TABLE_ACCOUNTS}`)
303
+ );
304
+ const supply = Array.isArray(supplyRows) ? supplyRows[0] : {};
305
+ const issuedRows = query(
306
+ sql(`SELECT COALESCE(SUM(amount),0) AS total FROM ${TABLE_TRANSACTIONS} WHERE target_player_id IS NOT NULL`)
307
+ );
308
+ const destroyedRows = query(
309
+ sql(`SELECT COALESCE(SUM(amount),0) AS total FROM ${TABLE_TRANSACTIONS} WHERE source_player_id IS NOT NULL`)
310
+ );
311
+ const issued = Array.isArray(issuedRows) ? Number(issuedRows[0].total ?? 0) : 0;
312
+ const destroyed = Array.isArray(destroyedRows) ? Number(destroyedRows[0].total ?? 0) : 0;
313
+ return {
314
+ id,
315
+ total_issued: issued,
316
+ total_destroyed: destroyed,
317
+ total_supply: Number(supply.total_supply ?? 0),
318
+ active_accounts: Number(supply.active_accounts ?? 0)
319
+ };
320
320
  }
321
- //# sourceMappingURL=economy.js.map
321
+ export {
322
+ applyEconomySteps,
323
+ applyEconomyTransaction,
324
+ economyResult,
325
+ ensureEconomyAccount,
326
+ getEconomyAccount,
327
+ listDailyTasks,
328
+ monthlyEconomyStats,
329
+ submitDailyTaskSteps,
330
+ submitDailyTaskTx
331
+ };
332
+ //# sourceMappingURL=economy.js.map