@openora/core 0.4.1-canary.60 → 0.4.1-canary.63

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 (217) hide show
  1. package/dist/.tsbuildinfo +1 -1
  2. package/dist/admin-console/plugin.d.ts +2 -0
  3. package/dist/admin-console/plugin.d.ts.map +1 -1
  4. package/dist/analytics/plugin.d.ts +2 -0
  5. package/dist/analytics/plugin.d.ts.map +1 -1
  6. package/dist/audit/plugin.d.ts +2 -0
  7. package/dist/audit/plugin.d.ts.map +1 -1
  8. package/dist/audit/plugin.js +13 -0
  9. package/dist/audit/plugin.js.map +1 -1
  10. package/dist/casino/gaming/plugin.d.ts +2 -0
  11. package/dist/casino/gaming/plugin.d.ts.map +1 -1
  12. package/dist/casino/lobby/plugin.d.ts +2 -0
  13. package/dist/casino/lobby/plugin.d.ts.map +1 -1
  14. package/dist/cms/plugin.d.ts +2 -0
  15. package/dist/cms/plugin.d.ts.map +1 -1
  16. package/dist/compliance/plugin.d.ts +2 -0
  17. package/dist/compliance/plugin.d.ts.map +1 -1
  18. package/dist/contracts/adapters/audit.d.ts +1 -1
  19. package/dist/contracts/adapters/audit.d.ts.map +1 -1
  20. package/dist/contracts/adapters/audit.js.map +1 -1
  21. package/dist/contracts/adapters/chat-system-writer.d.ts +7 -1
  22. package/dist/contracts/adapters/chat-system-writer.d.ts.map +1 -1
  23. package/dist/contracts/adapters/chat-system-writer.js.map +1 -1
  24. package/dist/contracts/adapters/gift-commands.d.ts +58 -0
  25. package/dist/contracts/adapters/gift-commands.d.ts.map +1 -0
  26. package/dist/contracts/adapters/gift-commands.js +11 -0
  27. package/dist/contracts/adapters/gift-commands.js.map +1 -0
  28. package/dist/contracts/adapters/index.d.ts +5 -1
  29. package/dist/contracts/adapters/index.d.ts.map +1 -1
  30. package/dist/contracts/adapters/index.js +2 -0
  31. package/dist/contracts/adapters/index.js.map +1 -1
  32. package/dist/contracts/adapters/rain-commands.d.ts +27 -0
  33. package/dist/contracts/adapters/rain-commands.d.ts.map +1 -0
  34. package/dist/contracts/adapters/rain-commands.js +12 -0
  35. package/dist/contracts/adapters/rain-commands.js.map +1 -0
  36. package/dist/contracts/schemas/chat-command-metadata.d.ts +26 -0
  37. package/dist/contracts/schemas/chat-command-metadata.d.ts.map +1 -1
  38. package/dist/contracts/schemas/chat-command-metadata.js +25 -0
  39. package/dist/contracts/schemas/chat-command-metadata.js.map +1 -1
  40. package/dist/contracts/schemas/chat-command.d.ts +126 -0
  41. package/dist/contracts/schemas/chat-command.d.ts.map +1 -1
  42. package/dist/contracts/schemas/chat-command.js +19 -1
  43. package/dist/contracts/schemas/chat-command.js.map +1 -1
  44. package/dist/contracts/schemas/events.d.ts +15 -3
  45. package/dist/contracts/schemas/events.d.ts.map +1 -1
  46. package/dist/contracts/schemas/events.js +17 -6
  47. package/dist/contracts/schemas/events.js.map +1 -1
  48. package/dist/contracts/schemas/identity.d.ts +2 -1
  49. package/dist/contracts/schemas/identity.d.ts.map +1 -1
  50. package/dist/contracts/schemas/identity.js +5 -1
  51. package/dist/contracts/schemas/identity.js.map +1 -1
  52. package/dist/engagement/chat/__tests__/chat.service.int.test.js +211 -8
  53. package/dist/engagement/chat/__tests__/chat.service.int.test.js.map +1 -1
  54. package/dist/engagement/chat/contract/index.d.ts +719 -40
  55. package/dist/engagement/chat/contract/index.d.ts.map +1 -1
  56. package/dist/engagement/chat/contract/index.js +51 -5
  57. package/dist/engagement/chat/contract/index.js.map +1 -1
  58. package/dist/engagement/chat/drizzle/migrations/0004_panoramic_robin_chapel.sql +6 -0
  59. package/dist/engagement/chat/drizzle/migrations/meta/0004_snapshot.json +666 -0
  60. package/dist/engagement/chat/drizzle/migrations/meta/_journal.json +7 -0
  61. package/dist/engagement/chat/plugin.d.ts +2 -0
  62. package/dist/engagement/chat/plugin.d.ts.map +1 -1
  63. package/dist/engagement/chat/router/index.d.ts +608 -34
  64. package/dist/engagement/chat/router/index.d.ts.map +1 -1
  65. package/dist/engagement/chat/router/index.js +10 -2
  66. package/dist/engagement/chat/router/index.js.map +1 -1
  67. package/dist/engagement/chat/schema/index.d.ts +72 -0
  68. package/dist/engagement/chat/schema/index.d.ts.map +1 -1
  69. package/dist/engagement/chat/schema/index.js +15 -3
  70. package/dist/engagement/chat/schema/index.js.map +1 -1
  71. package/dist/engagement/chat/service/chat.service.d.ts +421 -34
  72. package/dist/engagement/chat/service/chat.service.d.ts.map +1 -1
  73. package/dist/engagement/chat/service/chat.service.js +155 -40
  74. package/dist/engagement/chat/service/chat.service.js.map +1 -1
  75. package/dist/engagement/chat-commands/contract/index.d.ts +181 -73
  76. package/dist/engagement/chat-commands/contract/index.d.ts.map +1 -1
  77. package/dist/engagement/chat-commands/contract/index.js +50 -72
  78. package/dist/engagement/chat-commands/contract/index.js.map +1 -1
  79. package/dist/engagement/chat-commands/plugin.d.ts +3 -1
  80. package/dist/engagement/chat-commands/plugin.d.ts.map +1 -1
  81. package/dist/engagement/chat-commands/plugin.js +4 -4
  82. package/dist/engagement/chat-commands/plugin.js.map +1 -1
  83. package/dist/engagement/chat-commands/router/index.d.ts +185 -83
  84. package/dist/engagement/chat-commands/router/index.d.ts.map +1 -1
  85. package/dist/engagement/chat-commands/router/index.js +35 -25
  86. package/dist/engagement/chat-commands/router/index.js.map +1 -1
  87. package/dist/engagement/chat-commands/seed/index.d.ts.map +1 -1
  88. package/dist/engagement/chat-commands/seed/index.js +12 -0
  89. package/dist/engagement/chat-commands/seed/index.js.map +1 -1
  90. package/dist/engagement/chat-commands/service/__tests__/chat-commands.service.test.js +205 -733
  91. package/dist/engagement/chat-commands/service/__tests__/chat-commands.service.test.js.map +1 -1
  92. package/dist/engagement/chat-commands/service/chat-commands.service.d.ts +44 -68
  93. package/dist/engagement/chat-commands/service/chat-commands.service.d.ts.map +1 -1
  94. package/dist/engagement/chat-commands/service/chat-commands.service.js +143 -571
  95. package/dist/engagement/chat-commands/service/chat-commands.service.js.map +1 -1
  96. package/dist/engagement/contracts.d.ts +1 -0
  97. package/dist/engagement/contracts.d.ts.map +1 -1
  98. package/dist/engagement/contracts.js +1 -0
  99. package/dist/engagement/contracts.js.map +1 -1
  100. package/dist/engagement/index.d.ts +1 -0
  101. package/dist/engagement/index.d.ts.map +1 -1
  102. package/dist/engagement/index.js +1 -0
  103. package/dist/engagement/index.js.map +1 -1
  104. package/dist/engagement/notifications/plugin.d.ts +2 -0
  105. package/dist/engagement/notifications/plugin.d.ts.map +1 -1
  106. package/dist/engagement/server.d.ts +1 -0
  107. package/dist/engagement/server.d.ts.map +1 -1
  108. package/dist/engagement/server.js +1 -0
  109. package/dist/engagement/server.js.map +1 -1
  110. package/dist/engagement/social-transfers/contract/index.d.ts +85 -0
  111. package/dist/engagement/social-transfers/contract/index.d.ts.map +1 -0
  112. package/dist/engagement/social-transfers/contract/index.js +21 -0
  113. package/dist/engagement/social-transfers/contract/index.js.map +1 -0
  114. package/dist/engagement/social-transfers/drizzle/migrations/0000_clammy_rattler.sql +46 -0
  115. package/dist/engagement/social-transfers/drizzle/migrations/meta/0000_snapshot.json +287 -0
  116. package/dist/engagement/social-transfers/drizzle/migrations/meta/_journal.json +13 -0
  117. package/dist/engagement/social-transfers/drizzle.config.d.ts +4 -0
  118. package/dist/engagement/social-transfers/drizzle.config.d.ts.map +1 -0
  119. package/dist/engagement/social-transfers/drizzle.config.js +21 -0
  120. package/dist/engagement/social-transfers/drizzle.config.js.map +1 -0
  121. package/dist/engagement/social-transfers/index.d.ts +3 -0
  122. package/dist/engagement/social-transfers/index.d.ts.map +1 -0
  123. package/dist/engagement/social-transfers/index.js +6 -0
  124. package/dist/engagement/social-transfers/index.js.map +1 -0
  125. package/dist/engagement/social-transfers/migrate.d.ts +5 -0
  126. package/dist/engagement/social-transfers/migrate.d.ts.map +1 -0
  127. package/dist/engagement/social-transfers/migrate.js +17 -0
  128. package/dist/engagement/social-transfers/migrate.js.map +1 -0
  129. package/dist/engagement/social-transfers/plugin.d.ts +111 -0
  130. package/dist/engagement/social-transfers/plugin.d.ts.map +1 -0
  131. package/dist/engagement/social-transfers/plugin.js +24 -0
  132. package/dist/engagement/social-transfers/plugin.js.map +1 -0
  133. package/dist/engagement/social-transfers/router/index.d.ts +77 -0
  134. package/dist/engagement/social-transfers/router/index.d.ts.map +1 -0
  135. package/dist/engagement/social-transfers/router/index.js +25 -0
  136. package/dist/engagement/social-transfers/router/index.js.map +1 -0
  137. package/dist/engagement/social-transfers/schema/index.d.ts +609 -0
  138. package/dist/engagement/social-transfers/schema/index.d.ts.map +1 -0
  139. package/dist/engagement/social-transfers/schema/index.js +61 -0
  140. package/dist/engagement/social-transfers/schema/index.js.map +1 -0
  141. package/dist/engagement/social-transfers/service/__tests__/social-transfers.service.test.d.ts +2 -0
  142. package/dist/engagement/social-transfers/service/__tests__/social-transfers.service.test.d.ts.map +1 -0
  143. package/dist/engagement/social-transfers/service/__tests__/social-transfers.service.test.js +757 -0
  144. package/dist/engagement/social-transfers/service/__tests__/social-transfers.service.test.js.map +1 -0
  145. package/dist/engagement/social-transfers/service/social-transfers.service.d.ts +75 -0
  146. package/dist/engagement/social-transfers/service/social-transfers.service.d.ts.map +1 -0
  147. package/dist/engagement/social-transfers/service/social-transfers.service.js +789 -0
  148. package/dist/engagement/social-transfers/service/social-transfers.service.js.map +1 -0
  149. package/dist/iam/plugin.d.ts +2 -0
  150. package/dist/iam/plugin.d.ts.map +1 -1
  151. package/dist/iam/service/iam.service.d.ts +1 -1
  152. package/dist/pam/identity/__tests__/identity.service.int.test.js +47 -2
  153. package/dist/pam/identity/__tests__/identity.service.int.test.js.map +1 -1
  154. package/dist/pam/identity/__tests__/phone-login.service.int.test.js +23 -2
  155. package/dist/pam/identity/__tests__/phone-login.service.int.test.js.map +1 -1
  156. package/dist/pam/identity/plugin.d.ts +2 -0
  157. package/dist/pam/identity/plugin.d.ts.map +1 -1
  158. package/dist/pam/identity/service/identity.service.d.ts.map +1 -1
  159. package/dist/pam/identity/service/identity.service.js +29 -1
  160. package/dist/pam/identity/service/identity.service.js.map +1 -1
  161. package/dist/pam/identity/service/phone-login.service.d.ts.map +1 -1
  162. package/dist/pam/identity/service/phone-login.service.js +23 -0
  163. package/dist/pam/identity/service/phone-login.service.js.map +1 -1
  164. package/dist/pam/player-management/__tests__/player.router.int.test.js +27 -4
  165. package/dist/pam/player-management/__tests__/player.router.int.test.js.map +1 -1
  166. package/dist/pam/player-management/__tests__/player.service.int.test.js +159 -6
  167. package/dist/pam/player-management/__tests__/player.service.int.test.js.map +1 -1
  168. package/dist/pam/player-management/contract/index.d.ts +39 -0
  169. package/dist/pam/player-management/contract/index.d.ts.map +1 -1
  170. package/dist/pam/player-management/contract/index.js +33 -1
  171. package/dist/pam/player-management/contract/index.js.map +1 -1
  172. package/dist/pam/player-management/plugin.d.ts +3 -1
  173. package/dist/pam/player-management/plugin.d.ts.map +1 -1
  174. package/dist/pam/player-management/plugin.js +3 -3
  175. package/dist/pam/player-management/plugin.js.map +1 -1
  176. package/dist/pam/player-management/router/index.d.ts +21 -0
  177. package/dist/pam/player-management/router/index.d.ts.map +1 -1
  178. package/dist/pam/player-management/router/index.js +12 -2
  179. package/dist/pam/player-management/router/index.js.map +1 -1
  180. package/dist/pam/player-management/service/player.service.d.ts +10 -3
  181. package/dist/pam/player-management/service/player.service.d.ts.map +1 -1
  182. package/dist/pam/player-management/service/player.service.js +66 -3
  183. package/dist/pam/player-management/service/player.service.js.map +1 -1
  184. package/dist/pam/player-note/__tests__/player-note.router.int.test.d.ts +2 -0
  185. package/dist/pam/player-note/__tests__/player-note.router.int.test.d.ts.map +1 -0
  186. package/dist/pam/player-note/__tests__/player-note.router.int.test.js +52 -0
  187. package/dist/pam/player-note/__tests__/player-note.router.int.test.js.map +1 -0
  188. package/dist/pam/player-note/plugin.d.ts +49 -46
  189. package/dist/pam/player-note/plugin.d.ts.map +1 -1
  190. package/dist/pam/player-note/plugin.js +3 -1
  191. package/dist/pam/player-note/plugin.js.map +1 -1
  192. package/dist/pam/player-note/router/index.d.ts +2 -1
  193. package/dist/pam/player-note/router/index.d.ts.map +1 -1
  194. package/dist/pam/player-note/router/index.js +13 -2
  195. package/dist/pam/player-note/router/index.js.map +1 -1
  196. package/dist/pam/profile/plugin.d.ts +2 -0
  197. package/dist/pam/profile/plugin.d.ts.map +1 -1
  198. package/dist/pam/tag/plugin.d.ts +2 -0
  199. package/dist/pam/tag/plugin.d.ts.map +1 -1
  200. package/dist/server/auth/permissions.d.ts +24 -0
  201. package/dist/server/auth/permissions.d.ts.map +1 -1
  202. package/dist/server/auth/permissions.js +4 -0
  203. package/dist/server/auth/permissions.js.map +1 -1
  204. package/dist/server/runtime/core-token-catalog.d.ts +2 -0
  205. package/dist/server/runtime/core-token-catalog.d.ts.map +1 -1
  206. package/dist/server/runtime/core-token-catalog.js +3 -1
  207. package/dist/server/runtime/core-token-catalog.js.map +1 -1
  208. package/dist/wallet/__tests__/wallet.service.int.test.js +7 -0
  209. package/dist/wallet/__tests__/wallet.service.int.test.js.map +1 -1
  210. package/dist/wallet/plugin.d.ts +2 -0
  211. package/dist/wallet/plugin.d.ts.map +1 -1
  212. package/dist/wallet/router/index.js +1 -1
  213. package/dist/wallet/router/index.js.map +1 -1
  214. package/dist/wallet/service/wallet.service.d.ts.map +1 -1
  215. package/dist/wallet/service/wallet.service.js +16 -0
  216. package/dist/wallet/service/wallet.service.js.map +1 -1
  217. package/package.json +21 -1
@@ -1,61 +1,23 @@
1
- import { createHash } from 'node:crypto';
2
- import { and, eq, isNull, sql } from 'drizzle-orm';
3
- import { makeNotFoundError, makeConflictError, mapConcurrent, moneyToNumber, findOneOrThrow, serializeRow, createDomainError, } from '@openora/core/server';
1
+ import { eq, asc, desc, count } from 'drizzle-orm';
2
+ import { makeNotFoundError, makeConflictError, createDomainError, findOneOrThrow, pageToOffset, } from '@openora/core/server';
4
3
  import { chatChannel } from '@openora/core/contracts';
5
4
  import { ChatCommandTypeSchema } from '../contract/index.js';
6
- import { chatCommandConfig, chatGift } from '../schema/index.js';
5
+ import { chatCommandConfig } from '../schema/index.js';
7
6
  export const CommandDisabledError = makeNotFoundError('ChatCommand');
8
- export const NoOnlineUsersError = makeConflictError('NoOnlineUsers', 'No other users are online in this room');
7
+ export const ChatPlayerNotFoundError = makeNotFoundError('ChatPlayer');
9
8
  export const InsufficientBalanceError = makeConflictError('InsufficientBalance', 'Not enough balance');
10
9
  export const ExceedsLimitError = makeConflictError('ExceedsLimit', 'Amount exceeds the command limit');
11
10
  export const BelowMinimumError = makeConflictError('BelowMinimum', 'Amount is below the minimum for this command');
11
+ export const NoOnlineUsersError = makeConflictError('NoOnlineUsers', 'No other users are online in this room');
12
+ export const TooManyRecipientsError = makeConflictError('TooManyRecipients', 'Amount too small: you need at least $1 per recipient');
12
13
  export const RainCreditError = makeConflictError('RainCreditError', 'A recipient wallet is unavailable; rain aborted');
13
- export const ChatPlayerNotFoundError = makeNotFoundError('ChatPlayer');
14
14
  export const GiftNotFoundError = makeNotFoundError('ChatGift');
15
15
  export const GiftAlreadyClaimedError = makeConflictError('GiftAlreadyClaimed', 'This gift has already been claimed');
16
16
  export const GiftSelfClaimError = makeConflictError('GiftSelfClaim', 'You cannot claim your own gift');
17
- export const DonateSelfError = makeConflictError('DonateSelf', 'You cannot donate to yourself');
18
- export const SelfModerationActionError = makeConflictError('SelfModerationAction', 'You cannot block or ignore yourself');
19
- export const TooManyRecipientsError = makeConflictError('TooManyRecipients', 'Amount too small: you need at least $1 per recipient');
17
+ export const GiftCreditError = makeConflictError('GiftCreditError', 'Recipient wallet is unavailable; gift claim aborted');
20
18
  export const ChatCommandIdempotencyKeyReuseError = makeConflictError('ChatCommandIdempotencyKeyReuse', 'This idempotency key was already used with different request parameters');
21
19
  export const ConcurrentCommandReplayError = makeConflictError('ConcurrentCommandReplay', 'This request is already being processed - please retry');
22
- export const ChatRoomNotMemberError = createDomainError('ChatRoomNotMemberError', (roomId) => `You are not a member of room: ${roomId}`);
23
- const COMMAND_IDEMPOTENCY_TTL_MS = 5 * 60 * 1000;
24
- // The replay guard must match on the COMPLETE request, not just the amount - a reused
25
- // key with a different room, recipient count, or donate target is a distinct request,
26
- // not a replay of the original. `idempotencyKey` itself is excluded so the fingerprint
27
- // is stable for the row it guards.
28
- export function fingerprintCommand(input) {
29
- const canonical = input.type === 'gift'
30
- ? { type: input.type, amount: input.amount, roomId: input.roomId }
31
- : input.type === 'rain'
32
- ? {
33
- type: input.type,
34
- amount: input.amount,
35
- recipientCount: input.recipientCount,
36
- roomId: input.roomId,
37
- }
38
- : {
39
- type: input.type,
40
- amount: input.amount,
41
- targetUsername: input.targetUsername,
42
- roomId: input.roomId,
43
- };
44
- return createHash('sha256').update(JSON.stringify(canonical)).digest('hex');
45
- }
46
- function shuffleArray(arr) {
47
- const result = [...arr];
48
- for (let i = result.length - 1; i > 0; i--) {
49
- const j = Math.floor(Math.random() * (i + 1));
50
- const a = result.at(i);
51
- const b = result.at(j);
52
- if (a !== undefined && b !== undefined) {
53
- result[i] = b;
54
- result[j] = a;
55
- }
56
- }
57
- return result;
58
- }
20
+ export const ChatRoomNotMemberError = createDomainError('ChatRoomNotMemberError', (roomId) => roomId ? `You are not a member of room: ${roomId}` : 'You are not a member of global chat');
59
21
  function toDescriptor(row) {
60
22
  return {
61
23
  key: ChatCommandTypeSchema.parse(row.key),
@@ -63,34 +25,29 @@ function toDescriptor(row) {
63
25
  label: row.label,
64
26
  description: row.description ?? null,
65
27
  config: row.config ?? null,
28
+ updatedAt: row.updatedAt.toISOString(),
66
29
  };
67
30
  }
31
+ const ADMIN_COMMAND_SORT_COLUMNS = {
32
+ key: chatCommandConfig.key,
33
+ updatedAt: chatCommandConfig.updatedAt,
34
+ };
68
35
  export class ChatCommandsService {
69
36
  drizzle;
70
- systemWriter;
71
- wallet;
72
37
  directory;
73
- audit;
74
- transport;
75
- events;
76
38
  blockWriter;
77
- gameReporting;
78
- roomAccess;
79
- cache;
80
- idempotencyTtlMs;
81
- constructor(drizzle, systemWriter, wallet, directory, audit, transport, events, blockWriter, gameReporting, roomAccess, cache, idempotencyTtlMs = COMMAND_IDEMPOTENCY_TTL_MS) {
39
+ giftCommands;
40
+ rainCommands;
41
+ transport;
42
+ audit;
43
+ constructor(drizzle, directory, blockWriter, giftCommands, rainCommands, transport, audit) {
82
44
  this.drizzle = drizzle;
83
- this.systemWriter = systemWriter;
84
- this.wallet = wallet;
85
45
  this.directory = directory;
86
- this.audit = audit;
87
- this.transport = transport;
88
- this.events = events;
89
46
  this.blockWriter = blockWriter;
90
- this.gameReporting = gameReporting;
91
- this.roomAccess = roomAccess;
92
- this.cache = cache;
93
- this.idempotencyTtlMs = idempotencyTtlMs;
47
+ this.giftCommands = giftCommands;
48
+ this.rainCommands = rainCommands;
49
+ this.transport = transport;
50
+ this.audit = audit;
94
51
  }
95
52
  async listCommands(includeDisabled = false) {
96
53
  const rows = await this.drizzle.db
@@ -99,512 +56,19 @@ export class ChatCommandsService {
99
56
  .where(includeDisabled ? undefined : eq(chatCommandConfig.enabled, true));
100
57
  return rows.map(toDescriptor);
101
58
  }
102
- async searchMentions(q, limit, viewerId) {
103
- const ids = await this.directory.findPlayerIds(q, limit);
104
- if (ids.length === 0) {
105
- return [];
106
- }
107
- const excluded = new Set(await this.blockWriter.getExcludedUserIds(viewerId));
108
- const filteredIds = ids.filter((id) => !excluded.has(id));
109
- if (filteredIds.length === 0) {
110
- return [];
111
- }
112
- const summaries = await this.directory.lookupPlayers(filteredIds);
113
- return summaries.map((s) => ({ userId: s.userId, username: s.username }));
114
- }
115
- async searchPlayers(q, limit, viewerId) {
116
- const ids = await this.directory.findPlayerIds(q, limit);
117
- if (ids.length === 0) {
118
- return [];
119
- }
120
- const excluded = new Set(await this.blockWriter.getExcludedUserIds(viewerId));
121
- const filteredIds = ids.filter((id) => !excluded.has(id));
122
- if (filteredIds.length === 0) {
123
- return [];
124
- }
125
- const summaries = await this.directory.lookupPlayers(filteredIds);
126
- return summaries.map((s) => ({
127
- userId: s.userId,
128
- username: s.username,
129
- avatarUrl: s.avatarUrl,
130
- level: s.level,
131
- }));
132
- }
133
- async getPlayerProfile(userId, viewerId) {
134
- const summaries = await this.directory.lookupPlayers([userId]);
135
- const summary = summaries.find((s) => s.userId === userId);
136
- if (!summary) {
137
- throw new ChatPlayerNotFoundError(userId);
138
- }
139
- const isSelf = userId === viewerId;
140
- const stats = isSelf ? await this.gameReporting.getPlayerStats(userId) : null;
141
- return {
142
- userId: summary.userId,
143
- username: summary.username,
144
- avatarUrl: summary.avatarUrl,
145
- level: summary.level,
146
- joinedAt: isSelf ? summary.createdAt.toISOString() : null,
147
- totalWagered: stats?.totalWagered ?? null,
148
- totalBets: stats?.totalBets ?? null,
149
- currency: isSelf ? summary.currency : null,
150
- };
151
- }
152
- async executeCommand(input, actorId) {
153
- if (input.roomId) {
154
- try {
155
- await this.roomAccess.verifyRoomAccess(input.roomId, actorId);
156
- }
157
- catch (error) {
158
- if (error instanceof Error && error.name === 'ChatRoomNotMemberError') {
159
- throw new ChatRoomNotMemberError(input.roomId);
160
- }
161
- throw error;
162
- }
163
- }
164
- const [row] = await this.drizzle.db
165
- .select()
166
- .from(chatCommandConfig)
167
- .where(eq(chatCommandConfig.key, input.type))
168
- .limit(1);
169
- if (!row || !row.enabled) {
170
- throw new CommandDisabledError(input.type);
171
- }
172
- if (input.type === 'gift') {
173
- return this.handleGift(input, actorId, row.config ?? null);
174
- }
175
- if (input.type === 'donate') {
176
- return this.handleDonate(input, actorId, row.config ?? null);
177
- }
178
- if (input.type === 'block' || input.type === 'ignore') {
179
- return this.handleBlockAction(input, actorId);
180
- }
181
- if (input.type === 'rain') {
182
- return this.handleRain(input, actorId, row.config ?? null);
183
- }
184
- // exhaustive — TypeScript cannot narrow a union-literal discriminant ('block'|'ignore') away
185
- throw new CommandDisabledError(input.type);
186
- }
187
- async findCommandReplay(commandType, actorId, idempotencyKey, fingerprint) {
188
- const record = await this.cache.get(this.idempotencyCacheKey(commandType, actorId, idempotencyKey));
189
- if (!record) {
190
- return null;
191
- }
192
- if (record.fingerprint !== fingerprint) {
193
- throw new ChatCommandIdempotencyKeyReuseError();
194
- }
195
- if (!record.result) {
196
- throw new ConcurrentCommandReplayError();
197
- }
198
- return record.result;
199
- }
200
- idempotencyCacheKey(commandType, actorId, idempotencyKey) {
201
- return `chat-command:idempotency:${actorId}:${commandType}:${idempotencyKey}`;
202
- }
203
- async reserveCommandIdempotency(commandType, actorId, idempotencyKey, fingerprint) {
204
- const reserved = await this.cache.setIfAbsent(this.idempotencyCacheKey(commandType, actorId, idempotencyKey), { fingerprint, result: null }, { ttlMs: this.idempotencyTtlMs });
205
- if (!reserved) {
206
- throw new ConcurrentCommandReplayError();
207
- }
208
- }
209
- async completeCommandIdempotency(commandType, actorId, idempotencyKey, fingerprint, result) {
210
- await this.cache.set(this.idempotencyCacheKey(commandType, actorId, idempotencyKey), { fingerprint, result }, { ttlMs: this.idempotencyTtlMs });
211
- }
212
- async runWithCommandReservation(commandType, actorId, idempotencyKey, work) {
213
- try {
214
- return await work();
215
- }
216
- catch (error) {
217
- await this.cache.delete(this.idempotencyCacheKey(commandType, actorId, idempotencyKey));
218
- throw error;
219
- }
220
- }
221
- // Exact, case-insensitive username resolution for callers holding a complete, already-known
222
- // username (never a partial search term) - `/donate`, `/block`, `/ignore`. Distinct from
223
- // findPlayerIds' capped fuzzy substring search, which can silently drop the real match once
224
- // more than 20 unrelated accounts substring-collide with a short/common username.
225
- async resolveExactPlayer(username) {
226
- const summary = await this.directory.getPlayerByUsername(username);
227
- if (!summary) {
228
- throw new ChatPlayerNotFoundError(username);
229
- }
230
- return summary;
231
- }
232
- async handleGift(input, actorId, config) {
233
- if (config?.maxAmount !== undefined &&
234
- moneyToNumber(input.amount) > moneyToNumber(config.maxAmount)) {
235
- throw new ExceedsLimitError();
236
- }
237
- if (config?.minAmount !== undefined &&
238
- moneyToNumber(input.amount) < moneyToNumber(config.minAmount)) {
239
- throw new BelowMinimumError();
240
- }
241
- const fingerprint = fingerprintCommand(input);
242
- const replay = await this.findCommandReplay('gift', actorId, input.idempotencyKey, fingerprint);
243
- if (replay) {
244
- return replay;
245
- }
246
- await this.reserveCommandIdempotency('gift', actorId, input.idempotencyKey, fingerprint);
247
- // Resolve sender's username for the gift card metadata.
248
- const senderSummaries = await this.directory.lookupPlayers([actorId]);
249
- const senderSummary = senderSummaries.find((s) => s.userId === actorId);
250
- if (!senderSummary) {
251
- throw new ChatPlayerNotFoundError(actorId);
252
- }
253
- const senderUsername = senderSummary.username;
254
- const { msg, giftId, currency } = await this.runWithCommandReservation('gift', actorId, input.idempotencyKey, () => this.drizzle.db.transaction(async (tx) => {
255
- const debit = await this.wallet.debit(tx, {
256
- userId: actorId,
257
- amount: input.amount,
258
- type: 'gift',
259
- });
260
- if (!debit.ok) {
261
- throw new InsufficientBalanceError();
262
- }
263
- const [giftRow] = await tx
264
- .insert(chatGift)
265
- .values({
266
- senderId: actorId,
267
- senderUsername,
268
- amount: input.amount,
269
- currency: debit.currency,
270
- roomId: input.roomId,
271
- // messageId will be updated after postSystemMessage; we use a placeholder UUID
272
- // that is immediately overwritten by the UPDATE below in the same transaction.
273
- // Pattern: insert with a temporary value, then set the real foreign reference.
274
- messageId: '00000000-0000-0000-0000-000000000000',
275
- })
276
- .returning();
277
- if (!giftRow) {
278
- throw new InsufficientBalanceError();
279
- }
280
- const systemMsg = await this.systemWriter.postSystemMessage({
281
- roomId: input.roomId,
282
- actorId,
283
- tx,
284
- metadata: {
285
- command: 'gift',
286
- giftId: giftRow.id,
287
- senderId: actorId,
288
- senderUsername,
289
- amount: input.amount,
290
- currency: debit.currency,
291
- },
292
- });
293
- // Back-fill the real message id now that we have it.
294
- await tx
295
- .update(chatGift)
296
- .set({ messageId: systemMsg.id })
297
- .where(eq(chatGift.id, giftRow.id));
298
- await this.audit.recordInTransaction(tx, {
299
- actorId,
300
- actorType: 'player',
301
- action: 'chat.gift',
302
- resourceType: 'chat_gift',
303
- resourceId: giftRow.id,
304
- before: null,
305
- after: { amount: input.amount, roomId: input.roomId },
306
- });
307
- return { msg: systemMsg, giftId: giftRow.id, currency: debit.currency };
308
- }));
309
- await this.completeCommandIdempotency('gift', actorId, input.idempotencyKey, fingerprint, msg);
310
- // The caller now owns the commit boundary: postSystemMessage was passed `tx` above so
311
- // it did not auto-publish - publish only now that this transaction has committed.
312
- void this.transport.publish(chatChannel(input.roomId), msg);
313
- void this.events.emit('chat.gift.sent', {
314
- giftId,
315
- senderId: actorId,
316
- senderUsername,
317
- amount: input.amount,
318
- currency,
319
- roomId: input.roomId,
320
- messageId: msg.id,
321
- });
322
- return msg;
323
- }
324
- async getGift(giftId, viewerId) {
325
- const rows = await this.drizzle.db
326
- .select()
327
- .from(chatGift)
328
- .where(eq(chatGift.id, giftId))
329
- .limit(1);
330
- const row = findOneOrThrow(rows, new GiftNotFoundError(giftId));
331
- await this.roomAccess.verifyRoomAccess(row.roomId, viewerId);
332
- const serialized = serializeRow(row, {
333
- dateFields: ['claimedAt', 'createdAt'],
334
- decimalFields: ['amount'],
335
- });
336
- return {
337
- id: serialized.id,
338
- senderId: serialized.senderId,
339
- senderUsername: serialized.senderUsername,
340
- amount: serialized.amount,
341
- currency: serialized.currency,
342
- claimedBy: serialized.claimedBy ?? null,
343
- claimedByUsername: serialized.claimedByUsername ?? null,
344
- claimedAt: serialized.claimedAt ?? null,
345
- createdAt: serialized.createdAt,
346
- };
347
- }
348
- async claimGift(giftId, claimerId) {
349
- // Resolve claimer's username upfront — if the claimer is somehow not in the
350
- // directory that is a hard server error (defensive guard).
351
- const claimerSummaries = await this.directory.lookupPlayers([claimerId]);
352
- const claimerSummary = claimerSummaries.find((s) => s.userId === claimerId);
353
- if (!claimerSummary) {
354
- throw new GiftNotFoundError(claimerId);
355
- }
356
- const claimerUsername = claimerSummary.username;
357
- // Fetch gift to check self-claim BEFORE attempting the atomic update.
358
- const existing = await this.drizzle.db
359
- .select()
360
- .from(chatGift)
361
- .where(eq(chatGift.id, giftId))
362
- .limit(1);
363
- const giftRow = findOneOrThrow(existing, new GiftNotFoundError(giftId));
364
- await this.roomAccess.verifyRoomAccess(giftRow.roomId, claimerId);
365
- if (giftRow.senderId === claimerId) {
366
- throw new GiftSelfClaimError();
367
- }
368
- const { claimed, currency, roomId } = await this.drizzle.db.transaction(async (tx) => {
369
- const claimedAt = new Date();
370
- const results = await tx
371
- .update(chatGift)
372
- .set({ claimedBy: claimerId, claimedByUsername: claimerUsername, claimedAt })
373
- .where(and(eq(chatGift.id, giftId), isNull(chatGift.claimedBy)))
374
- .returning();
375
- if (results.length === 0) {
376
- // Another claimer won the race.
377
- throw new GiftAlreadyClaimedError();
378
- }
379
- const updated = findOneOrThrow(results, new GiftAlreadyClaimedError());
380
- const credit = await this.wallet.credit(tx, {
381
- userId: claimerId,
382
- amount: updated.amount,
383
- currency: updated.currency,
384
- type: 'gift',
385
- });
386
- if (!credit.ok) {
387
- throw new GiftNotFoundError(claimerId);
388
- }
389
- await this.audit.recordInTransaction(tx, {
390
- actorId: claimerId,
391
- actorType: 'player',
392
- action: 'chat.gift.claimed',
393
- resourceType: 'chat_gift',
394
- resourceId: giftId,
395
- before: null,
396
- after: { claimedBy: claimerId, amount: updated.amount },
397
- });
398
- return { claimed: updated, currency: updated.currency, roomId: updated.roomId };
399
- });
400
- void this.transport.publish(chatChannel(roomId), {
401
- event: 'gift.claimed',
402
- giftId,
403
- claimedBy: claimerId,
404
- claimedByUsername: claimerUsername,
405
- claimedAt: claimed.claimedAt?.toISOString() ?? new Date().toISOString(),
406
- });
407
- void this.events.emit('chat.gift.claimed', {
408
- giftId,
409
- claimerId,
410
- claimerUsername,
411
- senderId: giftRow.senderId,
412
- amount: claimed.amount,
413
- currency,
414
- roomId,
415
- });
416
- return {
417
- claimedBy: claimerId,
418
- claimedByUsername: claimerUsername,
419
- claimedAt: claimed.claimedAt?.toISOString() ?? new Date().toISOString(),
420
- };
421
- }
422
- async handleRain(input, actorId, config) {
423
- if (config?.maxAmount !== undefined &&
424
- moneyToNumber(input.amount) > moneyToNumber(config.maxAmount)) {
425
- throw new ExceedsLimitError();
426
- }
427
- if (config?.minAmount !== undefined &&
428
- moneyToNumber(input.amount) < moneyToNumber(config.minAmount)) {
429
- throw new BelowMinimumError();
430
- }
431
- const configMax = config?.maxRecipients ?? 50;
432
- if (input.recipientCount > configMax) {
433
- throw new ExceedsLimitError();
434
- }
435
- const amountUnits = Math.floor(moneyToNumber(input.amount));
436
- if (input.recipientCount > amountUnits) {
437
- throw new TooManyRecipientsError();
438
- }
439
- const fingerprint = fingerprintCommand(input);
440
- const replay = await this.findCommandReplay('rain', actorId, input.idempotencyKey, fingerprint);
441
- if (replay) {
442
- return replay;
443
- }
444
- await this.reserveCommandIdempotency('rain', actorId, input.idempotencyKey, fingerprint);
445
- const allOnline = await this.transport.getOnlineUserIds(chatChannel(input.roomId));
446
- const recipients = shuffleArray(allOnline.filter((id) => id !== actorId)).slice(0, input.recipientCount);
447
- if (recipients.length === 0) {
448
- throw new NoOnlineUsersError();
449
- }
450
- const { msg, currency, totalDistributed } = await this.runWithCommandReservation('rain', actorId, input.idempotencyKey, () => this.drizzle.db.transaction(async (tx) => {
451
- const splitResult = await tx.execute(sql `SELECT
452
- (floor(floor(${input.amount}::numeric) / ${recipients.length}))::text AS per_recipient,
453
- (floor(floor(${input.amount}::numeric) / ${recipients.length}) * ${recipients.length})::text AS total_distributed`);
454
- const { per_recipient: perRecipient, total_distributed: totalDistributed } = splitResult
455
- .rows[0];
456
- const debit = await this.wallet.debit(tx, {
457
- userId: actorId,
458
- amount: totalDistributed,
459
- type: 'rain',
460
- });
461
- if (!debit.ok) {
462
- throw new InsufficientBalanceError();
463
- }
464
- const credits = await mapConcurrent(recipients, 10, (userId) => this.wallet.credit(tx, {
465
- userId,
466
- amount: perRecipient,
467
- currency: debit.currency,
468
- type: 'rain',
469
- }));
470
- if (credits.some((c) => !c.ok)) {
471
- throw new RainCreditError();
472
- }
473
- const systemMsg = await this.systemWriter.postSystemMessage({
474
- roomId: input.roomId,
475
- actorId,
476
- tx,
477
- metadata: {
478
- command: 'rain',
479
- fromUserId: actorId,
480
- amount: totalDistributed,
481
- currency: debit.currency,
482
- recipientCount: recipients.length,
483
- perRecipient,
484
- },
485
- });
486
- await this.audit.recordInTransaction(tx, {
487
- actorId,
488
- actorType: 'player',
489
- action: 'chat.rain',
490
- resourceType: 'chat_room',
491
- resourceId: input.roomId,
492
- before: null,
493
- after: { amount: totalDistributed, recipientCount: recipients.length },
494
- });
495
- return { msg: systemMsg, currency: debit.currency, totalDistributed };
496
- }));
497
- await this.completeCommandIdempotency('rain', actorId, input.idempotencyKey, fingerprint, msg);
498
- void this.transport.publish(chatChannel(input.roomId), msg);
499
- void this.events.emit('chat.rain.distributed', {
500
- fromUserId: actorId,
501
- recipients,
502
- recipientCount: recipients.length,
503
- totalAmount: totalDistributed,
504
- currency,
505
- roomId: input.roomId,
506
- });
507
- return msg;
508
- }
509
- async handleDonate(input, actorId, config) {
510
- if (config?.maxAmount !== undefined &&
511
- moneyToNumber(input.amount) > moneyToNumber(config.maxAmount)) {
512
- throw new ExceedsLimitError();
513
- }
514
- if (config?.minAmount !== undefined &&
515
- moneyToNumber(input.amount) < moneyToNumber(config.minAmount)) {
516
- throw new BelowMinimumError();
517
- }
518
- const target = await this.resolveExactPlayer(input.targetUsername);
519
- if (target.userId === actorId) {
520
- throw new DonateSelfError();
521
- }
522
- const senderSummaries = await this.directory.lookupPlayers([actorId]);
523
- const sender = senderSummaries.find((s) => s.userId === actorId);
524
- if (!sender) {
525
- throw new ChatPlayerNotFoundError(actorId);
526
- }
527
- const fingerprint = fingerprintCommand(input);
528
- const replay = await this.findCommandReplay('donate', actorId, input.idempotencyKey, fingerprint);
529
- if (replay) {
530
- return replay;
531
- }
532
- await this.reserveCommandIdempotency('donate', actorId, input.idempotencyKey, fingerprint);
533
- const { msg, currency } = await this.runWithCommandReservation('donate', actorId, input.idempotencyKey, () => this.drizzle.db.transaction(async (tx) => {
534
- const debit = await this.wallet.debit(tx, {
535
- userId: actorId,
536
- amount: input.amount,
537
- type: 'tip',
538
- });
539
- if (!debit.ok) {
540
- throw new InsufficientBalanceError();
541
- }
542
- const credit = await this.wallet.credit(tx, {
543
- userId: target.userId,
544
- amount: input.amount,
545
- currency: debit.currency,
546
- type: 'tip',
547
- });
548
- if (!credit.ok) {
549
- throw new ChatPlayerNotFoundError(target.userId);
550
- }
551
- const systemMsg = await this.systemWriter.postSystemMessage({
552
- roomId: input.roomId,
553
- actorId,
554
- tx,
555
- metadata: {
556
- command: 'donate',
557
- recipientId: target.userId,
558
- recipientUsername: target.username,
559
- amount: input.amount,
560
- currency: debit.currency,
561
- },
562
- });
563
- await this.audit.recordInTransaction(tx, {
564
- actorId,
565
- actorType: 'player',
566
- action: 'chat.donate',
567
- resourceType: 'chat_donation',
568
- resourceId: systemMsg.id,
569
- before: null,
570
- after: { recipientId: target.userId, amount: input.amount, currency: debit.currency },
571
- });
572
- return { msg: systemMsg, currency: debit.currency };
573
- }));
574
- await this.completeCommandIdempotency('donate', actorId, input.idempotencyKey, fingerprint, msg);
575
- void this.transport.publish(chatChannel(input.roomId), msg);
576
- void this.events.emit('chat.donate.sent', {
577
- senderId: actorId,
578
- senderUsername: sender.username,
579
- recipientId: target.userId,
580
- recipientUsername: target.username,
581
- amount: input.amount,
582
- currency,
583
- roomId: input.roomId,
584
- });
585
- return msg;
586
- }
587
- /** Command must be used to update users that chat commands setup has been invalidated and needs to be refetched */
588
- async handleBlockAction(input, actorId) {
589
- const summary = await this.resolveExactPlayer(input.targetUsername);
590
- if (summary.userId === actorId) {
591
- throw new SelfModerationActionError();
592
- }
593
- if (input.type === 'block') {
594
- await this.blockWriter.blockUser(actorId, summary.userId);
595
- }
596
- else {
597
- await this.blockWriter.ignoreUser(actorId, summary.userId);
598
- }
599
- return this.systemWriter.postSystemMessage({
600
- roomId: input.roomId,
601
- actorId,
602
- metadata: {
603
- command: input.type,
604
- targetUserId: summary.userId,
605
- displayName: summary.username,
606
- },
607
- });
59
+ async adminListCommands({ page, limit, sortBy, sortOrder, }) {
60
+ const dir = sortOrder === 'asc' ? asc : desc;
61
+ const col = ADMIN_COMMAND_SORT_COLUMNS[sortBy];
62
+ const [rows, [{ n }]] = await Promise.all([
63
+ this.drizzle.db
64
+ .select()
65
+ .from(chatCommandConfig)
66
+ .orderBy(dir(col))
67
+ .limit(limit)
68
+ .offset(pageToOffset(page, limit)),
69
+ this.drizzle.db.select({ n: count() }).from(chatCommandConfig),
70
+ ]);
71
+ return { items: rows.map(toDescriptor), total: Number(n), page, limit };
608
72
  }
609
73
  async adminUpdateCommand(input, actorId) {
610
74
  const rows = await this.drizzle.db
@@ -637,5 +101,113 @@ export class ChatCommandsService {
637
101
  });
638
102
  return toDescriptor(row);
639
103
  }
104
+ async searchMentions(q, limit, viewerId) {
105
+ const ids = await this.directory.findPlayerIds(q, limit);
106
+ if (ids.length === 0) {
107
+ return [];
108
+ }
109
+ const excluded = new Set(await this.blockWriter.getExcludedUserIds(viewerId));
110
+ const filteredIds = ids.filter((id) => !excluded.has(id));
111
+ if (filteredIds.length === 0) {
112
+ return [];
113
+ }
114
+ const summaries = await this.directory.lookupPlayers(filteredIds);
115
+ return summaries.map((s) => ({ userId: s.userId, username: s.username }));
116
+ }
117
+ async postGift(input, actorId) {
118
+ const result = await this.giftCommands.sendGift(input, actorId);
119
+ if (result.ok) {
120
+ return result.message;
121
+ }
122
+ switch (result.reason) {
123
+ case 'disabled':
124
+ throw new CommandDisabledError('gift');
125
+ case 'player_not_found':
126
+ throw new ChatPlayerNotFoundError(result.playerId);
127
+ case 'insufficient_balance':
128
+ throw new InsufficientBalanceError();
129
+ case 'exceeds_limit':
130
+ throw new ExceedsLimitError();
131
+ case 'below_minimum':
132
+ throw new BelowMinimumError();
133
+ case 'idempotency_key_reuse':
134
+ throw new ChatCommandIdempotencyKeyReuseError();
135
+ case 'concurrent_replay':
136
+ throw new ConcurrentCommandReplayError();
137
+ case 'room_not_member':
138
+ throw new ChatRoomNotMemberError(input.roomId);
139
+ }
140
+ }
141
+ async claimGift(giftId, claimerId) {
142
+ const result = await this.giftCommands.claimGift(giftId, claimerId);
143
+ if (result.ok) {
144
+ return {
145
+ claimedBy: result.claimedBy,
146
+ claimedByUsername: result.claimedByUsername,
147
+ claimedAt: result.claimedAt,
148
+ };
149
+ }
150
+ switch (result.reason) {
151
+ case 'gift_not_found':
152
+ throw new GiftNotFoundError(giftId);
153
+ case 'already_claimed':
154
+ throw new GiftAlreadyClaimedError();
155
+ case 'self_claim':
156
+ throw new GiftSelfClaimError();
157
+ case 'room_not_member':
158
+ throw new ChatRoomNotMemberError(result.roomId ?? null);
159
+ case 'gift_credit_failed':
160
+ throw new GiftCreditError();
161
+ }
162
+ }
163
+ async getGift(giftId, viewerId) {
164
+ const result = await this.giftCommands.getGift(giftId, viewerId);
165
+ if (result.ok) {
166
+ return result.gift;
167
+ }
168
+ switch (result.reason) {
169
+ case 'gift_not_found':
170
+ throw new GiftNotFoundError(giftId);
171
+ case 'room_not_member':
172
+ throw new ChatRoomNotMemberError(result.roomId ?? null);
173
+ }
174
+ }
175
+ // Zero rain business logic here - the RAIN_COMMANDS port (bound by
176
+ // social-transfers) owns money movement, limit checks, idempotency, and
177
+ // posting/publishing the resulting chat message. This module only resolves
178
+ // who is online (it owns presence for the whole chat-command surface via
179
+ // its own dependency on `chat`) and translates the port's discriminated
180
+ // result into the typed errors this module's router maps to transport codes.
181
+ async postRain(input, actorId) {
182
+ const onlineUserIds = await this.transport.getOnlineUserIds(chatChannel(input.roomId));
183
+ const result = await this.rainCommands.sendRain({ ...input, onlineUserIds }, actorId);
184
+ if (result.ok) {
185
+ return result.message;
186
+ }
187
+ switch (result.reason) {
188
+ case 'disabled':
189
+ throw new CommandDisabledError('rain');
190
+ case 'player_not_found':
191
+ throw new ChatPlayerNotFoundError(result.playerId);
192
+ case 'insufficient_balance':
193
+ throw new InsufficientBalanceError();
194
+ case 'exceeds_limit':
195
+ throw new ExceedsLimitError();
196
+ case 'below_minimum':
197
+ throw new BelowMinimumError();
198
+ case 'no_online_users':
199
+ throw new NoOnlineUsersError();
200
+ case 'too_many_recipients':
201
+ throw new TooManyRecipientsError();
202
+ case 'rain_credit_failed':
203
+ throw new RainCreditError();
204
+ case 'idempotency_key_reuse':
205
+ throw new ChatCommandIdempotencyKeyReuseError();
206
+ case 'concurrent_replay':
207
+ throw new ConcurrentCommandReplayError();
208
+ case 'room_not_member':
209
+ throw new ChatRoomNotMemberError(input.roomId);
210
+ }
211
+ }
640
212
  }
641
213
  //# sourceMappingURL=chat-commands.service.js.map