@mtcute/dispatcher 0.1.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 (190) hide show
  1. package/LICENSE +8 -0
  2. package/README.md +26 -0
  3. package/cjs/callback-data-builder.d.ts +43 -0
  4. package/cjs/callback-data-builder.js +99 -0
  5. package/cjs/callback-data-builder.js.map +1 -0
  6. package/cjs/context/base.d.ts +8 -0
  7. package/cjs/context/base.js +3 -0
  8. package/cjs/context/base.js.map +1 -0
  9. package/cjs/context/callback-query.d.ts +27 -0
  10. package/cjs/context/callback-query.js +56 -0
  11. package/cjs/context/callback-query.js.map +1 -0
  12. package/cjs/context/chat-join-request.d.ts +16 -0
  13. package/cjs/context/chat-join-request.js +34 -0
  14. package/cjs/context/chat-join-request.js.map +1 -0
  15. package/cjs/context/chosen-inline-result.d.ts +21 -0
  16. package/cjs/context/chosen-inline-result.js +35 -0
  17. package/cjs/context/chosen-inline-result.js.map +1 -0
  18. package/cjs/context/index.d.ts +8 -0
  19. package/cjs/context/index.js +24 -0
  20. package/cjs/context/index.js.map +1 -0
  21. package/cjs/context/inline-query.d.ts +14 -0
  22. package/cjs/context/inline-query.js +22 -0
  23. package/cjs/context/inline-query.js.map +1 -0
  24. package/cjs/context/message.d.ts +72 -0
  25. package/cjs/context/message.js +142 -0
  26. package/cjs/context/message.js.map +1 -0
  27. package/cjs/context/parse.d.ts +1 -0
  28. package/cjs/context/parse.js +34 -0
  29. package/cjs/context/parse.js.map +1 -0
  30. package/cjs/context/pre-checkout-query.d.ts +16 -0
  31. package/cjs/context/pre-checkout-query.js +26 -0
  32. package/cjs/context/pre-checkout-query.js.map +1 -0
  33. package/cjs/dispatcher.d.ts +641 -0
  34. package/cjs/dispatcher.js +765 -0
  35. package/cjs/dispatcher.js.map +1 -0
  36. package/cjs/filters/bots.d.ts +70 -0
  37. package/cjs/filters/bots.js +129 -0
  38. package/cjs/filters/bots.js.map +1 -0
  39. package/cjs/filters/bundle.d.ts +10 -0
  40. package/cjs/filters/bundle.js +27 -0
  41. package/cjs/filters/bundle.js.map +1 -0
  42. package/cjs/filters/chat.d.ts +27 -0
  43. package/cjs/filters/chat.js +55 -0
  44. package/cjs/filters/chat.js.map +1 -0
  45. package/cjs/filters/group.d.ts +25 -0
  46. package/cjs/filters/group.js +72 -0
  47. package/cjs/filters/group.js.map +1 -0
  48. package/cjs/filters/index.d.ts +3 -0
  49. package/cjs/filters/index.js +29 -0
  50. package/cjs/filters/index.js.map +1 -0
  51. package/cjs/filters/logic.d.ts +29 -0
  52. package/cjs/filters/logic.js +114 -0
  53. package/cjs/filters/logic.js.map +1 -0
  54. package/cjs/filters/message.d.ts +295 -0
  55. package/cjs/filters/message.js +150 -0
  56. package/cjs/filters/message.js.map +1 -0
  57. package/cjs/filters/state.d.ts +15 -0
  58. package/cjs/filters/state.js +32 -0
  59. package/cjs/filters/state.js.map +1 -0
  60. package/cjs/filters/text.d.ts +64 -0
  61. package/cjs/filters/text.js +132 -0
  62. package/cjs/filters/text.js.map +1 -0
  63. package/cjs/filters/types.d.ts +91 -0
  64. package/cjs/filters/types.js +6 -0
  65. package/cjs/filters/types.js.map +1 -0
  66. package/cjs/filters/updates.d.ts +46 -0
  67. package/cjs/filters/updates.js +46 -0
  68. package/cjs/filters/updates.js.map +1 -0
  69. package/cjs/filters/user.d.ts +24 -0
  70. package/cjs/filters/user.js +76 -0
  71. package/cjs/filters/user.js.map +1 -0
  72. package/cjs/handler.d.ts +31 -0
  73. package/cjs/handler.js +4 -0
  74. package/cjs/handler.js.map +1 -0
  75. package/cjs/index.d.ts +8 -0
  76. package/cjs/index.js +25 -0
  77. package/cjs/index.js.map +1 -0
  78. package/cjs/package.json +3 -0
  79. package/cjs/propagation.d.ts +21 -0
  80. package/cjs/propagation.js +26 -0
  81. package/cjs/propagation.js.map +1 -0
  82. package/cjs/state/index.d.ts +3 -0
  83. package/cjs/state/index.js +20 -0
  84. package/cjs/state/index.js.map +1 -0
  85. package/cjs/state/key.d.ts +23 -0
  86. package/cjs/state/key.js +45 -0
  87. package/cjs/state/key.js.map +1 -0
  88. package/cjs/state/storage.d.ts +75 -0
  89. package/cjs/state/storage.js +17 -0
  90. package/cjs/state/storage.js.map +1 -0
  91. package/cjs/state/update-state.d.ts +151 -0
  92. package/cjs/state/update-state.js +211 -0
  93. package/cjs/state/update-state.js.map +1 -0
  94. package/cjs/wizard.d.ts +60 -0
  95. package/cjs/wizard.js +103 -0
  96. package/cjs/wizard.js.map +1 -0
  97. package/esm/callback-data-builder.d.ts +43 -0
  98. package/esm/callback-data-builder.js +95 -0
  99. package/esm/callback-data-builder.js.map +1 -0
  100. package/esm/context/base.d.ts +8 -0
  101. package/esm/context/base.js +2 -0
  102. package/esm/context/base.js.map +1 -0
  103. package/esm/context/callback-query.d.ts +27 -0
  104. package/esm/context/callback-query.js +52 -0
  105. package/esm/context/callback-query.js.map +1 -0
  106. package/esm/context/chat-join-request.d.ts +16 -0
  107. package/esm/context/chat-join-request.js +30 -0
  108. package/esm/context/chat-join-request.js.map +1 -0
  109. package/esm/context/chosen-inline-result.d.ts +21 -0
  110. package/esm/context/chosen-inline-result.js +31 -0
  111. package/esm/context/chosen-inline-result.js.map +1 -0
  112. package/esm/context/index.d.ts +8 -0
  113. package/esm/context/index.js +8 -0
  114. package/esm/context/index.js.map +1 -0
  115. package/esm/context/inline-query.d.ts +14 -0
  116. package/esm/context/inline-query.js +18 -0
  117. package/esm/context/inline-query.js.map +1 -0
  118. package/esm/context/message.d.ts +72 -0
  119. package/esm/context/message.js +138 -0
  120. package/esm/context/message.js.map +1 -0
  121. package/esm/context/parse.d.ts +1 -0
  122. package/esm/context/parse.js +30 -0
  123. package/esm/context/parse.js.map +1 -0
  124. package/esm/context/pre-checkout-query.d.ts +16 -0
  125. package/esm/context/pre-checkout-query.js +22 -0
  126. package/esm/context/pre-checkout-query.js.map +1 -0
  127. package/esm/dispatcher.d.ts +641 -0
  128. package/esm/dispatcher.js +761 -0
  129. package/esm/dispatcher.js.map +1 -0
  130. package/esm/filters/bots.d.ts +70 -0
  131. package/esm/filters/bots.js +125 -0
  132. package/esm/filters/bots.js.map +1 -0
  133. package/esm/filters/bundle.d.ts +10 -0
  134. package/esm/filters/bundle.js +11 -0
  135. package/esm/filters/bundle.js.map +1 -0
  136. package/esm/filters/chat.d.ts +27 -0
  137. package/esm/filters/chat.js +50 -0
  138. package/esm/filters/chat.js.map +1 -0
  139. package/esm/filters/group.d.ts +25 -0
  140. package/esm/filters/group.js +67 -0
  141. package/esm/filters/group.js.map +1 -0
  142. package/esm/filters/index.d.ts +3 -0
  143. package/esm/filters/index.js +3 -0
  144. package/esm/filters/index.js.map +1 -0
  145. package/esm/filters/logic.d.ts +29 -0
  146. package/esm/filters/logic.js +107 -0
  147. package/esm/filters/logic.js.map +1 -0
  148. package/esm/filters/message.d.ts +295 -0
  149. package/esm/filters/message.js +130 -0
  150. package/esm/filters/message.js.map +1 -0
  151. package/esm/filters/state.d.ts +15 -0
  152. package/esm/filters/state.js +27 -0
  153. package/esm/filters/state.js.map +1 -0
  154. package/esm/filters/text.d.ts +64 -0
  155. package/esm/filters/text.js +124 -0
  156. package/esm/filters/text.js.map +1 -0
  157. package/esm/filters/types.d.ts +91 -0
  158. package/esm/filters/types.js +5 -0
  159. package/esm/filters/types.js.map +1 -0
  160. package/esm/filters/updates.d.ts +46 -0
  161. package/esm/filters/updates.js +39 -0
  162. package/esm/filters/updates.js.map +1 -0
  163. package/esm/filters/user.d.ts +24 -0
  164. package/esm/filters/user.js +70 -0
  165. package/esm/filters/user.js.map +1 -0
  166. package/esm/handler.d.ts +31 -0
  167. package/esm/handler.js +3 -0
  168. package/esm/handler.js.map +1 -0
  169. package/esm/index.d.ts +8 -0
  170. package/esm/index.js +9 -0
  171. package/esm/index.js.map +1 -0
  172. package/esm/propagation.d.ts +21 -0
  173. package/esm/propagation.js +23 -0
  174. package/esm/propagation.js.map +1 -0
  175. package/esm/state/index.d.ts +3 -0
  176. package/esm/state/index.js +4 -0
  177. package/esm/state/index.js.map +1 -0
  178. package/esm/state/key.d.ts +23 -0
  179. package/esm/state/key.js +41 -0
  180. package/esm/state/key.js.map +1 -0
  181. package/esm/state/storage.d.ts +75 -0
  182. package/esm/state/storage.js +13 -0
  183. package/esm/state/storage.js.map +1 -0
  184. package/esm/state/update-state.d.ts +151 -0
  185. package/esm/state/update-state.js +206 -0
  186. package/esm/state/update-state.js.map +1 -0
  187. package/esm/wizard.d.ts +60 -0
  188. package/esm/wizard.js +99 -0
  189. package/esm/wizard.js.map +1 -0
  190. package/package.json +21 -0
@@ -0,0 +1,23 @@
1
+ /**
2
+ * Propagation action.
3
+ *
4
+ * `Stop`: Stop the propagation of the event through any handler groups
5
+ * in the current dispatcher. Does not prevent child dispatchers from
6
+ * being executed.
7
+ *
8
+ * `StopChildren`: Stop the propagation of the event through any handler groups
9
+ * in the current dispatcher, and any of its children. If current dispatcher
10
+ * is a child, does not prevent from propagating to its siblings.
11
+ *
12
+ * `Continue`: Continue propagating the event inside the same handler group.
13
+ *
14
+ * `ToScene`: Used after using `state.enter()` to dispatch the update to the scene
15
+ */
16
+ export var PropagationAction;
17
+ (function (PropagationAction) {
18
+ PropagationAction["Stop"] = "stop";
19
+ PropagationAction["StopChildren"] = "stop-children";
20
+ PropagationAction["Continue"] = "continue";
21
+ PropagationAction["ToScene"] = "scene";
22
+ })(PropagationAction = PropagationAction || (PropagationAction = {}));
23
+ //# sourceMappingURL=propagation.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"propagation.js","sourceRoot":"","sources":["../../src/propagation.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AACH,MAAM,CAAN,IAAY,iBAKX;AALD,WAAY,iBAAiB;IACzB,kCAAa,CAAA;IACb,mDAA8B,CAAA;IAC9B,0CAAqB,CAAA;IACrB,sCAAiB,CAAA;AACrB,CAAC,EALW,iBAAiB,GAAjB,iBAAiB,KAAjB,iBAAiB,QAK5B","sourcesContent":["/**\n * Propagation action.\n *\n * `Stop`: Stop the propagation of the event through any handler groups\n * in the current dispatcher. Does not prevent child dispatchers from\n * being executed.\n *\n * `StopChildren`: Stop the propagation of the event through any handler groups\n * in the current dispatcher, and any of its children. If current dispatcher\n * is a child, does not prevent from propagating to its siblings.\n *\n * `Continue`: Continue propagating the event inside the same handler group.\n *\n * `ToScene`: Used after using `state.enter()` to dispatch the update to the scene\n */\nexport enum PropagationAction {\n Stop = 'stop',\n StopChildren = 'stop-children',\n Continue = 'continue',\n ToScene = 'scene',\n}\n"]}
@@ -0,0 +1,3 @@
1
+ export * from './key.js';
2
+ export * from './storage.js';
3
+ export * from './update-state.js';
@@ -0,0 +1,4 @@
1
+ export * from './key.js';
2
+ export * from './storage.js';
3
+ export * from './update-state.js';
4
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/state/index.ts"],"names":[],"mappings":"AAAA,cAAc,UAAU,CAAA;AACxB,cAAc,cAAc,CAAA;AAC5B,cAAc,mBAAmB,CAAA","sourcesContent":["export * from './key.js'\nexport * from './storage.js'\nexport * from './update-state.js'\n"]}
@@ -0,0 +1,23 @@
1
+ import { Chat, MaybeAsync, User } from '@mtcute/client';
2
+ import { CallbackQueryContext, MessageContext } from '../context/index.js';
3
+ /**
4
+ * Function that determines how the state key is derived.
5
+ *
6
+ * The key is additionally prefixed with current scene, if any.
7
+ *
8
+ * @param msg Message or callback from which to derive the key
9
+ * @param scene Current scene UID, or `null` if none
10
+ */
11
+ export type StateKeyDelegate = (upd: MessageContext | CallbackQueryContext | User | Chat) => MaybeAsync<string | null>;
12
+ /**
13
+ * Default state key delegate.
14
+ *
15
+ * Derives key as follows:
16
+ * - If private chat, `msg.chat.id`
17
+ * - If group chat, `msg.chat.id + '_' + msg.sender.id`
18
+ * - If channel, `msg.chat.id`
19
+ * - If non-inline callback query:
20
+ * - If in private chat (i.e. `upd.chatType === 'user'`), `upd.user.id`
21
+ * - If in group/channel/supergroup (i.e. `upd.chatType !== 'user'`), `upd.chatId + '_' + upd.user.id`
22
+ */
23
+ export declare const defaultStateKeyDelegate: StateKeyDelegate;
@@ -0,0 +1,41 @@
1
+ import { assertNever } from '@mtcute/client';
2
+ /**
3
+ * Default state key delegate.
4
+ *
5
+ * Derives key as follows:
6
+ * - If private chat, `msg.chat.id`
7
+ * - If group chat, `msg.chat.id + '_' + msg.sender.id`
8
+ * - If channel, `msg.chat.id`
9
+ * - If non-inline callback query:
10
+ * - If in private chat (i.e. `upd.chatType === 'user'`), `upd.user.id`
11
+ * - If in group/channel/supergroup (i.e. `upd.chatType !== 'user'`), `upd.chatId + '_' + upd.user.id`
12
+ */
13
+ export const defaultStateKeyDelegate = (upd) => {
14
+ if ('type' in upd) {
15
+ // User | Chat
16
+ return String(upd.id);
17
+ }
18
+ if (upd._name === 'new_message') {
19
+ switch (upd.chat.chatType) {
20
+ case 'private':
21
+ case 'bot':
22
+ case 'channel':
23
+ return String(upd.chat.id);
24
+ case 'group':
25
+ case 'supergroup':
26
+ case 'gigagroup':
27
+ return `${upd.chat.id}_${upd.sender.id}`;
28
+ default:
29
+ assertNever(upd.chat.chatType);
30
+ }
31
+ }
32
+ if (upd._name === 'callback_query') {
33
+ if (upd.isInline)
34
+ return null;
35
+ if (upd.chatType === 'user')
36
+ return `${upd.user.id}`;
37
+ return `${upd.chatId}_${upd.user.id}`;
38
+ }
39
+ return null;
40
+ };
41
+ //# sourceMappingURL=key.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"key.js","sourceRoot":"","sources":["../../../src/state/key.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAA0B,MAAM,gBAAgB,CAAA;AAcpE;;;;;;;;;;GAUG;AACH,MAAM,CAAC,MAAM,uBAAuB,GAAqB,CAAC,GAAG,EAAiB,EAAE;IAC5E,IAAI,MAAM,IAAI,GAAG,EAAE;QACf,cAAc;QACd,OAAO,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC,CAAA;KACxB;IAED,IAAI,GAAG,CAAC,KAAK,KAAK,aAAa,EAAE;QAC7B,QAAQ,GAAG,CAAC,IAAI,CAAC,QAAQ,EAAE;YACvB,KAAK,SAAS,CAAC;YACf,KAAK,KAAK,CAAC;YACX,KAAK,SAAS;gBACV,OAAO,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC,CAAA;YAC9B,KAAK,OAAO,CAAC;YACb,KAAK,YAAY,CAAC;YAClB,KAAK,WAAW;gBACZ,OAAO,GAAG,GAAG,CAAC,IAAI,CAAC,EAAE,IAAI,GAAG,CAAC,MAAM,CAAC,EAAE,EAAE,CAAA;YAC5C;gBACI,WAAW,CAAC,GAAG,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAA;SACrC;KACJ;IAED,IAAI,GAAG,CAAC,KAAK,KAAK,gBAAgB,EAAE;QAChC,IAAI,GAAG,CAAC,QAAQ;YAAE,OAAO,IAAI,CAAA;QAC7B,IAAI,GAAG,CAAC,QAAQ,KAAK,MAAM;YAAE,OAAO,GAAG,GAAG,CAAC,IAAI,CAAC,EAAE,EAAE,CAAA;QAEpD,OAAO,GAAG,GAAG,CAAC,MAAM,IAAI,GAAG,CAAC,IAAI,CAAC,EAAE,EAAE,CAAA;KACxC;IAED,OAAO,IAAI,CAAA;AACf,CAAC,CAAA","sourcesContent":["import { assertNever, Chat, MaybeAsync, User } from '@mtcute/client'\n\nimport { CallbackQueryContext, MessageContext } from '../context/index.js'\n\n/**\n * Function that determines how the state key is derived.\n *\n * The key is additionally prefixed with current scene, if any.\n *\n * @param msg Message or callback from which to derive the key\n * @param scene Current scene UID, or `null` if none\n */\nexport type StateKeyDelegate = (upd: MessageContext | CallbackQueryContext | User | Chat) => MaybeAsync<string | null>\n\n/**\n * Default state key delegate.\n *\n * Derives key as follows:\n * - If private chat, `msg.chat.id`\n * - If group chat, `msg.chat.id + '_' + msg.sender.id`\n * - If channel, `msg.chat.id`\n * - If non-inline callback query:\n * - If in private chat (i.e. `upd.chatType === 'user'`), `upd.user.id`\n * - If in group/channel/supergroup (i.e. `upd.chatType !== 'user'`), `upd.chatId + '_' + upd.user.id`\n */\nexport const defaultStateKeyDelegate: StateKeyDelegate = (upd): string | null => {\n if ('type' in upd) {\n // User | Chat\n return String(upd.id)\n }\n\n if (upd._name === 'new_message') {\n switch (upd.chat.chatType) {\n case 'private':\n case 'bot':\n case 'channel':\n return String(upd.chat.id)\n case 'group':\n case 'supergroup':\n case 'gigagroup':\n return `${upd.chat.id}_${upd.sender.id}`\n default:\n assertNever(upd.chat.chatType)\n }\n }\n\n if (upd._name === 'callback_query') {\n if (upd.isInline) return null\n if (upd.chatType === 'user') return `${upd.user.id}`\n\n return `${upd.chatId}_${upd.user.id}`\n }\n\n return null\n}\n"]}
@@ -0,0 +1,75 @@
1
+ import { MaybeAsync } from '@mtcute/client';
2
+ /**
3
+ * Interface for FSM storage for the dispatcher.
4
+ *
5
+ * All of the officially supported storages already implement
6
+ * this interface, so you can just re-use it.
7
+ *
8
+ * Current scene is a special case of a `string` state,
9
+ * Most of the time you can just store it the same way
10
+ * as normal state, prefixing with something like `$current_state_`
11
+ * (scene name can't start with `$`).
12
+ * Alternatively, you can store them as simple strings
13
+ */
14
+ export interface IStateStorage {
15
+ /**
16
+ * Retrieve state from the storage
17
+ *
18
+ * @param key Key of the state, as defined by {@link StateKeyDelegate}
19
+ */
20
+ getState(key: string): MaybeAsync<unknown>;
21
+ /**
22
+ * Save state to the storage
23
+ *
24
+ * @param key Key of the state, as defined by {@link StateKeyDelegate}
25
+ * @param state Object representing the state
26
+ * @param ttl TTL for the state, in seconds
27
+ */
28
+ setState(key: string, state: unknown, ttl?: number): MaybeAsync<void>;
29
+ /**
30
+ * Delete state from the storage
31
+ *
32
+ * @param key Key of the state, as defined by {@link StateKeyDelegate}
33
+ */
34
+ deleteState(key: string): MaybeAsync<void>;
35
+ /**
36
+ * Retrieve the current scene UID from the storage
37
+ *
38
+ * @param key Key of the state, as defined by {@link StateKeyDelegate}
39
+ */
40
+ getCurrentScene(key: string): MaybeAsync<string | null>;
41
+ /**
42
+ * Change current scene's UID from the storage
43
+ *
44
+ * @param key Key of the state, as defined by {@link StateKeyDelegate}
45
+ * @param scene New scene
46
+ * @param ttl TTL for the scene, in seconds
47
+ */
48
+ setCurrentScene(key: string, scene: string, ttl?: number): MaybeAsync<void>;
49
+ /**
50
+ * Delete current scene from the storage, effectively "exiting" to root.
51
+ *
52
+ * @param key Key of the state, as defined by {@link StateKeyDelegate}
53
+ */
54
+ deleteCurrentScene(key: string): MaybeAsync<void>;
55
+ /**
56
+ * Get information about a rate limit.
57
+ *
58
+ * It is recommended that you use sliding window or leaky bucket
59
+ * to implement rate limiting ([learn more](https://konghq.com/blog/how-to-design-a-scalable-rate-limiting-algorithm/)),
60
+ *
61
+ * @param key Key of the rate limit
62
+ * @param limit Maximum number of requests in `window`
63
+ * @param window Window size in seconds
64
+ * @returns Tuple containing the number of remaining and
65
+ * unix time in ms when the user can try again
66
+ */
67
+ getRateLimit(key: string, limit: number, window: number): MaybeAsync<[number, number]>;
68
+ /**
69
+ * Reset a rate limit.
70
+ *
71
+ * @param key Key of the rate limit
72
+ */
73
+ resetRateLimit(key: string): MaybeAsync<void>;
74
+ }
75
+ export declare function isCompatibleStorage(storage: unknown): storage is IStateStorage;
@@ -0,0 +1,13 @@
1
+ export function isCompatibleStorage(storage) {
2
+ return (typeof storage === 'object' &&
3
+ storage !== null &&
4
+ 'getState' in storage &&
5
+ 'setState' in storage &&
6
+ 'deleteState' in storage &&
7
+ 'getCurrentScene' in storage &&
8
+ 'setCurrentScene' in storage &&
9
+ 'deleteCurrentScene' in storage &&
10
+ 'getRateLimit' in storage &&
11
+ 'resetRateLimit' in storage);
12
+ }
13
+ //# sourceMappingURL=storage.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"storage.js","sourceRoot":"","sources":["../../../src/state/storage.ts"],"names":[],"mappings":"AAmFA,MAAM,UAAU,mBAAmB,CAAC,OAAgB;IAChD,OAAO,CACH,OAAO,OAAO,KAAK,QAAQ;QAC3B,OAAO,KAAK,IAAI;QAChB,UAAU,IAAI,OAAO;QACrB,UAAU,IAAI,OAAO;QACrB,aAAa,IAAI,OAAO;QACxB,iBAAiB,IAAI,OAAO;QAC5B,iBAAiB,IAAI,OAAO;QAC5B,oBAAoB,IAAI,OAAO;QAC/B,cAAc,IAAI,OAAO;QACzB,gBAAgB,IAAI,OAAO,CAC9B,CAAA;AACL,CAAC","sourcesContent":["import { MaybeAsync } from '@mtcute/client'\n\n/**\n * Interface for FSM storage for the dispatcher.\n *\n * All of the officially supported storages already implement\n * this interface, so you can just re-use it.\n *\n * Current scene is a special case of a `string` state,\n * Most of the time you can just store it the same way\n * as normal state, prefixing with something like `$current_state_`\n * (scene name can't start with `$`).\n * Alternatively, you can store them as simple strings\n */\nexport interface IStateStorage {\n /**\n * Retrieve state from the storage\n *\n * @param key Key of the state, as defined by {@link StateKeyDelegate}\n */\n getState(key: string): MaybeAsync<unknown>\n\n /**\n * Save state to the storage\n *\n * @param key Key of the state, as defined by {@link StateKeyDelegate}\n * @param state Object representing the state\n * @param ttl TTL for the state, in seconds\n */\n setState(key: string, state: unknown, ttl?: number): MaybeAsync<void>\n\n /**\n * Delete state from the storage\n *\n * @param key Key of the state, as defined by {@link StateKeyDelegate}\n */\n deleteState(key: string): MaybeAsync<void>\n\n /**\n * Retrieve the current scene UID from the storage\n *\n * @param key Key of the state, as defined by {@link StateKeyDelegate}\n */\n getCurrentScene(key: string): MaybeAsync<string | null>\n\n /**\n * Change current scene's UID from the storage\n *\n * @param key Key of the state, as defined by {@link StateKeyDelegate}\n * @param scene New scene\n * @param ttl TTL for the scene, in seconds\n */\n setCurrentScene(key: string, scene: string, ttl?: number): MaybeAsync<void>\n\n /**\n * Delete current scene from the storage, effectively \"exiting\" to root.\n *\n * @param key Key of the state, as defined by {@link StateKeyDelegate}\n */\n deleteCurrentScene(key: string): MaybeAsync<void>\n\n /**\n * Get information about a rate limit.\n *\n * It is recommended that you use sliding window or leaky bucket\n * to implement rate limiting ([learn more](https://konghq.com/blog/how-to-design-a-scalable-rate-limiting-algorithm/)),\n *\n * @param key Key of the rate limit\n * @param limit Maximum number of requests in `window`\n * @param window Window size in seconds\n * @returns Tuple containing the number of remaining and\n * unix time in ms when the user can try again\n */\n getRateLimit(key: string, limit: number, window: number): MaybeAsync<[number, number]>\n\n /**\n * Reset a rate limit.\n *\n * @param key Key of the rate limit\n */\n resetRateLimit(key: string): MaybeAsync<void>\n}\n\nexport function isCompatibleStorage(storage: unknown): storage is IStateStorage {\n return (\n typeof storage === 'object' &&\n storage !== null &&\n 'getState' in storage &&\n 'setState' in storage &&\n 'deleteState' in storage &&\n 'getCurrentScene' in storage &&\n 'setCurrentScene' in storage &&\n 'deleteCurrentScene' in storage &&\n 'getRateLimit' in storage &&\n 'resetRateLimit' in storage\n )\n}\n"]}
@@ -0,0 +1,151 @@
1
+ import { MtcuteError } from '@mtcute/client';
2
+ import type { Dispatcher } from '../dispatcher.js';
3
+ import { IStateStorage } from './storage.js';
4
+ /**
5
+ * Error thrown by `.rateLimit()`
6
+ */
7
+ export declare class RateLimitError extends MtcuteError {
8
+ readonly reset: number;
9
+ constructor(reset: number);
10
+ }
11
+ /**
12
+ * State of the current update.
13
+ *
14
+ * @template State Type that represents the state
15
+ * @template SceneName Possible scene names
16
+ */
17
+ export declare class UpdateState<State extends object> {
18
+ private _key;
19
+ private _localKey;
20
+ private _storage;
21
+ private _scene;
22
+ private _scoped?;
23
+ private _cached?;
24
+ private _localStorage;
25
+ private _localKeyBase;
26
+ constructor(storage: IStateStorage, key: string, scene: string | null, scoped?: boolean, customStorage?: IStateStorage, customKey?: string);
27
+ /** Name of the current scene */
28
+ get scene(): string | null;
29
+ private _updateLocalKey;
30
+ /**
31
+ * Retrieve the state from the storage, falling back to default
32
+ * if not found
33
+ *
34
+ * @param fallback Default state value
35
+ * @param force Whether to ignore cached state (def. `false`)
36
+ */
37
+ get(fallback: State | (() => State), force?: boolean): Promise<State>;
38
+ /**
39
+ * Retrieve the state from the storage, falling back to default
40
+ * if not found
41
+ *
42
+ * @param fallback Default state value
43
+ * @param force Whether to ignore cached state (def. `false`)
44
+ */
45
+ get(fallback?: State | (() => State), force?: boolean): Promise<State | null>;
46
+ /**
47
+ * Retrieve the state from the storage
48
+ *
49
+ * @param force Whether to ignore cached state (def. `false`)
50
+ */
51
+ get(force?: boolean): Promise<State | null>;
52
+ /**
53
+ * Set new state to the storage
54
+ *
55
+ * @param state New state
56
+ * @param ttl TTL for the new state (in seconds)
57
+ */
58
+ set(state: State, ttl?: number): Promise<void>;
59
+ /**
60
+ * Merge the given object to the current state.
61
+ *
62
+ * > **Note**: If the storage currently has no state,
63
+ * > then `fallback` must be provided.
64
+ *
65
+ * Basically a shorthand to calling `.get()`,
66
+ * modifying and then calling `.set()`
67
+ *
68
+ * @param state State to be merged
69
+ * @param fallback Default state
70
+ * @param ttl TTL for the new state (in seconds)
71
+ * @param forceLoad Whether to force load the old state from storage
72
+ */
73
+ merge(state: Partial<State>, params?: {
74
+ fallback?: State | (() => State);
75
+ ttl?: number;
76
+ forceLoad?: boolean;
77
+ }): Promise<State>;
78
+ /**
79
+ * Delete the state from the storage
80
+ */
81
+ delete(): Promise<void>;
82
+ /**
83
+ * Enter some scene
84
+ */
85
+ enter<SceneState extends object, Scene extends Dispatcher<SceneState>>(scene: Scene, params?: {
86
+ /**
87
+ * Initial state for the scene
88
+ *
89
+ * Note that this will only work if the scene uses the same key delegate as this state.
90
+ */
91
+ with?: SceneState;
92
+ /** TTL for the scene (in seconds) */
93
+ ttl?: number;
94
+ /**
95
+ * If currently in a scoped scene, whether to reset the state
96
+ *
97
+ * @default true
98
+ */
99
+ reset?: boolean;
100
+ }): Promise<void>;
101
+ /**
102
+ * Exit from current scene to the root
103
+ *
104
+ * @param reset
105
+ * Whether to reset scene state (only applicable in case this is a scoped scene)
106
+ */
107
+ exit(reset?: boolean): Promise<void>;
108
+ /**
109
+ * Rate limit some handler.
110
+ *
111
+ * When the rate limit exceeds, {@link RateLimitError} is thrown.
112
+ *
113
+ * This is a simple rate-limiting solution that uses
114
+ * the same key as the state. If you need something more
115
+ * sophisticated and/or customizable, you'll have to implement
116
+ * your own rate-limiter.
117
+ *
118
+ * > **Note**: `key` is used to prefix the local key
119
+ * > derived using the given key delegate.
120
+ *
121
+ * @param key Key of the rate limit
122
+ * @param limit Maximum number of requests in `window`
123
+ * @param window Window size in seconds
124
+ * @returns Tuple containing the number of remaining and
125
+ * unix time in ms when the user can try again
126
+ */
127
+ rateLimit(key: string, limit: number, window: number): Promise<[number, number]>;
128
+ /**
129
+ * Throttle some handler.
130
+ *
131
+ * When the rate limit exceeds, this function waits for it to reset.
132
+ *
133
+ * This is a simple wrapper over {@link rateLimit}, and follows the same logic.
134
+ *
135
+ * > **Note**: `key` is used to prefix the local key
136
+ * > derived using the given key delegate.
137
+ *
138
+ * @param key Key of the rate limit
139
+ * @param limit Maximum number of requests in `window`
140
+ * @param window Window size in seconds
141
+ * @returns Tuple containing the number of remaining and
142
+ * unix time in ms when the user can try again
143
+ */
144
+ throttle(key: string, limit: number, window: number): Promise<[number, number]>;
145
+ /**
146
+ * Reset the rate limit
147
+ *
148
+ * @param key Key of the rate limit
149
+ */
150
+ resetRateLimit(key: string): Promise<void>;
151
+ }
@@ -0,0 +1,206 @@
1
+ /* eslint-disable dot-notation */
2
+ /* eslint-disable @typescript-eslint/no-explicit-any */
3
+ import { MtArgumentError, MtcuteError } from '@mtcute/client';
4
+ import { sleep } from '@mtcute/client/utils.js';
5
+ /**
6
+ * Error thrown by `.rateLimit()`
7
+ */
8
+ export class RateLimitError extends MtcuteError {
9
+ constructor(reset) {
10
+ super('You are being rate limited.');
11
+ this.reset = reset;
12
+ }
13
+ }
14
+ /**
15
+ * State of the current update.
16
+ *
17
+ * @template State Type that represents the state
18
+ * @template SceneName Possible scene names
19
+ */
20
+ export class UpdateState {
21
+ constructor(storage, key, scene, scoped, customStorage, customKey) {
22
+ this._storage = storage;
23
+ this._key = key;
24
+ this._scene = scene;
25
+ this._scoped = scoped;
26
+ this._localStorage = customStorage ?? storage;
27
+ this._localKeyBase = customKey ?? key;
28
+ this._updateLocalKey();
29
+ }
30
+ /** Name of the current scene */
31
+ get scene() {
32
+ return this._scene;
33
+ }
34
+ _updateLocalKey() {
35
+ if (!this._scoped)
36
+ this._localKey = this._localKeyBase;
37
+ else {
38
+ this._localKey = this._scene ? this._scene + '_' + this._localKeyBase : this._localKeyBase;
39
+ }
40
+ }
41
+ async get(fallback, force) {
42
+ if (typeof fallback === 'boolean') {
43
+ force = fallback;
44
+ fallback = undefined;
45
+ }
46
+ if (!force && this._cached !== undefined) {
47
+ if (!this._cached && fallback) {
48
+ return typeof fallback === 'function' ? fallback() : fallback;
49
+ }
50
+ return this._cached;
51
+ }
52
+ let res = (await this._localStorage.getState(this._localKey));
53
+ if (!res && fallback) {
54
+ res = typeof fallback === 'function' ? fallback() : fallback;
55
+ }
56
+ this._cached = res;
57
+ return res;
58
+ }
59
+ /**
60
+ * Set new state to the storage
61
+ *
62
+ * @param state New state
63
+ * @param ttl TTL for the new state (in seconds)
64
+ */
65
+ async set(state, ttl) {
66
+ this._cached = state;
67
+ await this._localStorage.setState(this._localKey, state, ttl);
68
+ }
69
+ /**
70
+ * Merge the given object to the current state.
71
+ *
72
+ * > **Note**: If the storage currently has no state,
73
+ * > then `fallback` must be provided.
74
+ *
75
+ * Basically a shorthand to calling `.get()`,
76
+ * modifying and then calling `.set()`
77
+ *
78
+ * @param state State to be merged
79
+ * @param fallback Default state
80
+ * @param ttl TTL for the new state (in seconds)
81
+ * @param forceLoad Whether to force load the old state from storage
82
+ */
83
+ async merge(state, params = {}) {
84
+ const { fallback, ttl, forceLoad } = params;
85
+ const old = await this.get(forceLoad);
86
+ if (!old) {
87
+ if (!fallback) {
88
+ throw new MtArgumentError('Cannot use merge on empty state without fallback.');
89
+ }
90
+ const fallback_ = typeof fallback === 'function' ? fallback() : fallback;
91
+ await this.set({ ...fallback_, ...state }, ttl);
92
+ }
93
+ else {
94
+ await this.set({ ...old, ...state }, ttl);
95
+ }
96
+ // _cached is set by .set()
97
+ return this._cached;
98
+ }
99
+ /**
100
+ * Delete the state from the storage
101
+ */
102
+ async delete() {
103
+ this._cached = null;
104
+ await this._localStorage.deleteState(this._localKey);
105
+ }
106
+ /**
107
+ * Enter some scene
108
+ */
109
+ async enter(scene, params) {
110
+ const { with: with_, ttl, reset = true } = params ?? {};
111
+ if (reset && this._scoped)
112
+ await this.delete();
113
+ if (!scene['_scene']) {
114
+ throw new MtArgumentError('Cannot enter a non-scene Dispatcher');
115
+ }
116
+ if (!scene['_parent']) {
117
+ throw new MtArgumentError('This scene has not been registered');
118
+ }
119
+ this._scene = scene['_scene'];
120
+ this._scoped = scene['_sceneScoped'];
121
+ this._updateLocalKey();
122
+ await this._storage.setCurrentScene(this._key, this._scene, ttl);
123
+ if (with_) {
124
+ if (scene['_customStateKeyDelegate']) {
125
+ throw new MtArgumentError('Cannot use `with` parameter when the scene uses a custom state key delegate');
126
+ }
127
+ await scene.getState(this._key).set(with_, ttl);
128
+ }
129
+ }
130
+ /**
131
+ * Exit from current scene to the root
132
+ *
133
+ * @param reset
134
+ * Whether to reset scene state (only applicable in case this is a scoped scene)
135
+ */
136
+ async exit(reset = true) {
137
+ if (reset && this._scoped)
138
+ await this.delete();
139
+ this._scene = null;
140
+ this._updateLocalKey();
141
+ await this._storage.deleteCurrentScene(this._key);
142
+ }
143
+ /**
144
+ * Rate limit some handler.
145
+ *
146
+ * When the rate limit exceeds, {@link RateLimitError} is thrown.
147
+ *
148
+ * This is a simple rate-limiting solution that uses
149
+ * the same key as the state. If you need something more
150
+ * sophisticated and/or customizable, you'll have to implement
151
+ * your own rate-limiter.
152
+ *
153
+ * > **Note**: `key` is used to prefix the local key
154
+ * > derived using the given key delegate.
155
+ *
156
+ * @param key Key of the rate limit
157
+ * @param limit Maximum number of requests in `window`
158
+ * @param window Window size in seconds
159
+ * @returns Tuple containing the number of remaining and
160
+ * unix time in ms when the user can try again
161
+ */
162
+ async rateLimit(key, limit, window) {
163
+ const [remaining, reset] = await this._localStorage.getRateLimit(`${key}:${this._localKey}`, limit, window);
164
+ if (!remaining) {
165
+ throw new RateLimitError(reset);
166
+ }
167
+ return [remaining - 1, reset];
168
+ }
169
+ /**
170
+ * Throttle some handler.
171
+ *
172
+ * When the rate limit exceeds, this function waits for it to reset.
173
+ *
174
+ * This is a simple wrapper over {@link rateLimit}, and follows the same logic.
175
+ *
176
+ * > **Note**: `key` is used to prefix the local key
177
+ * > derived using the given key delegate.
178
+ *
179
+ * @param key Key of the rate limit
180
+ * @param limit Maximum number of requests in `window`
181
+ * @param window Window size in seconds
182
+ * @returns Tuple containing the number of remaining and
183
+ * unix time in ms when the user can try again
184
+ */
185
+ async throttle(key, limit, window) {
186
+ try {
187
+ return await this.rateLimit(key, limit, window);
188
+ }
189
+ catch (e) {
190
+ if (e instanceof RateLimitError) {
191
+ await sleep(e.reset - Date.now());
192
+ return this.throttle(key, limit, window);
193
+ }
194
+ throw e;
195
+ }
196
+ }
197
+ /**
198
+ * Reset the rate limit
199
+ *
200
+ * @param key Key of the rate limit
201
+ */
202
+ async resetRateLimit(key) {
203
+ await this._localStorage.resetRateLimit(`${key}:${this._localKey}`);
204
+ }
205
+ }
206
+ //# sourceMappingURL=update-state.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"update-state.js","sourceRoot":"","sources":["../../../src/state/update-state.ts"],"names":[],"mappings":"AAAA,iCAAiC;AACjC,uDAAuD;AACvD,OAAO,EAAE,eAAe,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAA;AAC7D,OAAO,EAAE,KAAK,EAAE,MAAM,yBAAyB,CAAA;AAK/C;;GAEG;AACH,MAAM,OAAO,cAAe,SAAQ,WAAW;IAC3C,YAAqB,KAAa;QAC9B,KAAK,CAAC,6BAA6B,CAAC,CAAA;QADnB,UAAK,GAAL,KAAK,CAAQ;IAElC,CAAC;CACJ;AAED;;;;;GAKG;AACH,MAAM,OAAO,WAAW;IAapB,YACI,OAAsB,EACtB,GAAW,EACX,KAAoB,EACpB,MAAgB,EAChB,aAA6B,EAC7B,SAAkB;QAElB,IAAI,CAAC,QAAQ,GAAG,OAAO,CAAA;QACvB,IAAI,CAAC,IAAI,GAAG,GAAG,CAAA;QACf,IAAI,CAAC,MAAM,GAAG,KAAK,CAAA;QACnB,IAAI,CAAC,OAAO,GAAG,MAAM,CAAA;QAErB,IAAI,CAAC,aAAa,GAAG,aAAa,IAAI,OAAO,CAAA;QAC7C,IAAI,CAAC,aAAa,GAAG,SAAS,IAAI,GAAG,CAAA;QAErC,IAAI,CAAC,eAAe,EAAE,CAAA;IAC1B,CAAC;IAED,gCAAgC;IAChC,IAAI,KAAK;QACL,OAAO,IAAI,CAAC,MAAM,CAAA;IACtB,CAAC;IAEO,eAAe;QACnB,IAAI,CAAC,IAAI,CAAC,OAAO;YAAE,IAAI,CAAC,SAAS,GAAG,IAAI,CAAC,aAAa,CAAA;aACjD;YACD,IAAI,CAAC,SAAS,GAAG,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,MAAM,GAAG,GAAG,GAAG,IAAI,CAAC,aAAa,CAAC,CAAC,CAAC,IAAI,CAAC,aAAa,CAAA;SAC7F;IACL,CAAC;IAyBD,KAAK,CAAC,GAAG,CAAC,QAA0C,EAAE,KAAe;QACjE,IAAI,OAAO,QAAQ,KAAK,SAAS,EAAE;YAC/B,KAAK,GAAG,QAAQ,CAAA;YAChB,QAAQ,GAAG,SAAS,CAAA;SACvB;QAED,IAAI,CAAC,KAAK,IAAI,IAAI,CAAC,OAAO,KAAK,SAAS,EAAE;YACtC,IAAI,CAAC,IAAI,CAAC,OAAO,IAAI,QAAQ,EAAE;gBAC3B,OAAO,OAAO,QAAQ,KAAK,UAAU,CAAC,CAAC,CAAC,QAAQ,EAAE,CAAC,CAAC,CAAC,QAAQ,CAAA;aAChE;YAED,OAAO,IAAI,CAAC,OAAO,CAAA;SACtB;QAED,IAAI,GAAG,GAAG,CAAC,MAAM,IAAI,CAAC,aAAa,CAAC,QAAQ,CAAC,IAAI,CAAC,SAAS,CAAC,CAAiB,CAAA;QAE7E,IAAI,CAAC,GAAG,IAAI,QAAQ,EAAE;YAClB,GAAG,GAAG,OAAO,QAAQ,KAAK,UAAU,CAAC,CAAC,CAAC,QAAQ,EAAE,CAAC,CAAC,CAAC,QAAQ,CAAA;SAC/D;QACD,IAAI,CAAC,OAAO,GAAG,GAAG,CAAA;QAElB,OAAO,GAAG,CAAA;IACd,CAAC;IAED;;;;;OAKG;IACH,KAAK,CAAC,GAAG,CAAC,KAAY,EAAE,GAAY;QAChC,IAAI,CAAC,OAAO,GAAG,KAAK,CAAA;QACpB,MAAM,IAAI,CAAC,aAAa,CAAC,QAAQ,CAAC,IAAI,CAAC,SAAS,EAAE,KAAK,EAAE,GAAG,CAAC,CAAA;IACjE,CAAC;IAED;;;;;;;;;;;;;OAaG;IACH,KAAK,CAAC,KAAK,CACP,KAAqB,EACrB,SAII,EAAE;QAEN,MAAM,EAAE,QAAQ,EAAE,GAAG,EAAE,SAAS,EAAE,GAAG,MAAM,CAAA;QAE3C,MAAM,GAAG,GAAG,MAAM,IAAI,CAAC,GAAG,CAAC,SAAS,CAAC,CAAA;QAErC,IAAI,CAAC,GAAG,EAAE;YACN,IAAI,CAAC,QAAQ,EAAE;gBACX,MAAM,IAAI,eAAe,CAAC,mDAAmD,CAAC,CAAA;aACjF;YAED,MAAM,SAAS,GAAG,OAAO,QAAQ,KAAK,UAAU,CAAC,CAAC,CAAC,QAAQ,EAAE,CAAC,CAAC,CAAC,QAAQ,CAAA;YAExE,MAAM,IAAI,CAAC,GAAG,CAAC,EAAE,GAAG,SAAS,EAAE,GAAG,KAAK,EAAE,EAAE,GAAG,CAAC,CAAA;SAClD;aAAM;YACH,MAAM,IAAI,CAAC,GAAG,CAAC,EAAE,GAAG,GAAG,EAAE,GAAG,KAAK,EAAE,EAAE,GAAG,CAAC,CAAA;SAC5C;QAED,2BAA2B;QAE3B,OAAO,IAAI,CAAC,OAAQ,CAAA;IACxB,CAAC;IAED;;OAEG;IACH,KAAK,CAAC,MAAM;QACR,IAAI,CAAC,OAAO,GAAG,IAAI,CAAA;QACnB,MAAM,IAAI,CAAC,aAAa,CAAC,WAAW,CAAC,IAAI,CAAC,SAAS,CAAC,CAAA;IACxD,CAAC;IAED;;OAEG;IACH,KAAK,CAAC,KAAK,CACP,KAAY,EACZ,MAiBC;QAED,MAAM,EAAE,IAAI,EAAE,KAAK,EAAE,GAAG,EAAE,KAAK,GAAG,IAAI,EAAE,GAAG,MAAM,IAAI,EAAE,CAAA;QAEvD,IAAI,KAAK,IAAI,IAAI,CAAC,OAAO;YAAE,MAAM,IAAI,CAAC,MAAM,EAAE,CAAA;QAE9C,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC,EAAE;YAClB,MAAM,IAAI,eAAe,CAAC,qCAAqC,CAAC,CAAA;SACnE;QAED,IAAI,CAAC,KAAK,CAAC,SAAS,CAAC,EAAE;YACnB,MAAM,IAAI,eAAe,CAAC,oCAAoC,CAAC,CAAA;SAClE;QAED,IAAI,CAAC,MAAM,GAAG,KAAK,CAAC,QAAQ,CAAC,CAAA;QAC7B,IAAI,CAAC,OAAO,GAAG,KAAK,CAAC,cAAc,CAAC,CAAA;QACpC,IAAI,CAAC,eAAe,EAAE,CAAA;QAEtB,MAAM,IAAI,CAAC,QAAQ,CAAC,eAAe,CAAC,IAAI,CAAC,IAAI,EAAE,IAAI,CAAC,MAAM,EAAE,GAAG,CAAC,CAAA;QAEhE,IAAI,KAAK,EAAE;YACP,IAAI,KAAK,CAAC,yBAAyB,CAAC,EAAE;gBAClC,MAAM,IAAI,eAAe,CAAC,6EAA6E,CAAC,CAAA;aAC3G;YAED,MAAM,KAAK,CAAC,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,KAAK,EAAE,GAAG,CAAC,CAAA;SAClD;IACL,CAAC;IAED;;;;;OAKG;IACH,KAAK,CAAC,IAAI,CAAC,KAAK,GAAG,IAAI;QACnB,IAAI,KAAK,IAAI,IAAI,CAAC,OAAO;YAAE,MAAM,IAAI,CAAC,MAAM,EAAE,CAAA;QAC9C,IAAI,CAAC,MAAM,GAAG,IAAI,CAAA;QAClB,IAAI,CAAC,eAAe,EAAE,CAAA;QACtB,MAAM,IAAI,CAAC,QAAQ,CAAC,kBAAkB,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;IACrD,CAAC;IAED;;;;;;;;;;;;;;;;;;OAkBG;IACH,KAAK,CAAC,SAAS,CAAC,GAAW,EAAE,KAAa,EAAE,MAAc;QACtD,MAAM,CAAC,SAAS,EAAE,KAAK,CAAC,GAAG,MAAM,IAAI,CAAC,aAAa,CAAC,YAAY,CAAC,GAAG,GAAG,IAAI,IAAI,CAAC,SAAS,EAAE,EAAE,KAAK,EAAE,MAAM,CAAC,CAAA;QAE3G,IAAI,CAAC,SAAS,EAAE;YACZ,MAAM,IAAI,cAAc,CAAC,KAAK,CAAC,CAAA;SAClC;QAED,OAAO,CAAC,SAAS,GAAG,CAAC,EAAE,KAAK,CAAC,CAAA;IACjC,CAAC;IAED;;;;;;;;;;;;;;;OAeG;IACH,KAAK,CAAC,QAAQ,CAAC,GAAW,EAAE,KAAa,EAAE,MAAc;QACrD,IAAI;YACA,OAAO,MAAM,IAAI,CAAC,SAAS,CAAC,GAAG,EAAE,KAAK,EAAE,MAAM,CAAC,CAAA;SAClD;QAAC,OAAO,CAAU,EAAE;YACjB,IAAI,CAAC,YAAY,cAAc,EAAE;gBAC7B,MAAM,KAAK,CAAC,CAAC,CAAC,KAAK,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC,CAAA;gBAEjC,OAAO,IAAI,CAAC,QAAQ,CAAC,GAAG,EAAE,KAAK,EAAE,MAAM,CAAC,CAAA;aAC3C;YAED,MAAM,CAAC,CAAA;SACV;IACL,CAAC;IAED;;;;OAIG;IACH,KAAK,CAAC,cAAc,CAAC,GAAW;QAC5B,MAAM,IAAI,CAAC,aAAa,CAAC,cAAc,CAAC,GAAG,GAAG,IAAI,IAAI,CAAC,SAAS,EAAE,CAAC,CAAA;IACvE,CAAC;CACJ","sourcesContent":["/* eslint-disable dot-notation */\n/* eslint-disable @typescript-eslint/no-explicit-any */\nimport { MtArgumentError, MtcuteError } from '@mtcute/client'\nimport { sleep } from '@mtcute/client/utils.js'\n\nimport type { Dispatcher } from '../dispatcher.js'\nimport { IStateStorage } from './storage.js'\n\n/**\n * Error thrown by `.rateLimit()`\n */\nexport class RateLimitError extends MtcuteError {\n constructor(readonly reset: number) {\n super('You are being rate limited.')\n }\n}\n\n/**\n * State of the current update.\n *\n * @template State Type that represents the state\n * @template SceneName Possible scene names\n */\nexport class UpdateState<State extends object> {\n private _key: string\n private _localKey!: string\n\n private _storage: IStateStorage\n\n private _scene: string | null\n private _scoped?: boolean\n private _cached?: State | null\n\n private _localStorage: IStateStorage\n private _localKeyBase: string\n\n constructor(\n storage: IStateStorage,\n key: string,\n scene: string | null,\n scoped?: boolean,\n customStorage?: IStateStorage,\n customKey?: string,\n ) {\n this._storage = storage\n this._key = key\n this._scene = scene\n this._scoped = scoped\n\n this._localStorage = customStorage ?? storage\n this._localKeyBase = customKey ?? key\n\n this._updateLocalKey()\n }\n\n /** Name of the current scene */\n get scene(): string | null {\n return this._scene\n }\n\n private _updateLocalKey(): void {\n if (!this._scoped) this._localKey = this._localKeyBase\n else {\n this._localKey = this._scene ? this._scene + '_' + this._localKeyBase : this._localKeyBase\n }\n }\n\n /**\n * Retrieve the state from the storage, falling back to default\n * if not found\n *\n * @param fallback Default state value\n * @param force Whether to ignore cached state (def. `false`)\n */\n async get(fallback: State | (() => State), force?: boolean): Promise<State>\n\n /**\n * Retrieve the state from the storage, falling back to default\n * if not found\n *\n * @param fallback Default state value\n * @param force Whether to ignore cached state (def. `false`)\n */\n async get(fallback?: State | (() => State), force?: boolean): Promise<State | null>\n /**\n * Retrieve the state from the storage\n *\n * @param force Whether to ignore cached state (def. `false`)\n */\n async get(force?: boolean): Promise<State | null>\n async get(fallback?: State | (() => State) | boolean, force?: boolean): Promise<State | null> {\n if (typeof fallback === 'boolean') {\n force = fallback\n fallback = undefined\n }\n\n if (!force && this._cached !== undefined) {\n if (!this._cached && fallback) {\n return typeof fallback === 'function' ? fallback() : fallback\n }\n\n return this._cached\n }\n\n let res = (await this._localStorage.getState(this._localKey)) as State | null\n\n if (!res && fallback) {\n res = typeof fallback === 'function' ? fallback() : fallback\n }\n this._cached = res\n\n return res\n }\n\n /**\n * Set new state to the storage\n *\n * @param state New state\n * @param ttl TTL for the new state (in seconds)\n */\n async set(state: State, ttl?: number): Promise<void> {\n this._cached = state\n await this._localStorage.setState(this._localKey, state, ttl)\n }\n\n /**\n * Merge the given object to the current state.\n *\n * > **Note**: If the storage currently has no state,\n * > then `fallback` must be provided.\n *\n * Basically a shorthand to calling `.get()`,\n * modifying and then calling `.set()`\n *\n * @param state State to be merged\n * @param fallback Default state\n * @param ttl TTL for the new state (in seconds)\n * @param forceLoad Whether to force load the old state from storage\n */\n async merge(\n state: Partial<State>,\n params: {\n fallback?: State | (() => State)\n ttl?: number\n forceLoad?: boolean\n } = {},\n ): Promise<State> {\n const { fallback, ttl, forceLoad } = params\n\n const old = await this.get(forceLoad)\n\n if (!old) {\n if (!fallback) {\n throw new MtArgumentError('Cannot use merge on empty state without fallback.')\n }\n\n const fallback_ = typeof fallback === 'function' ? fallback() : fallback\n\n await this.set({ ...fallback_, ...state }, ttl)\n } else {\n await this.set({ ...old, ...state }, ttl)\n }\n\n // _cached is set by .set()\n\n return this._cached!\n }\n\n /**\n * Delete the state from the storage\n */\n async delete(): Promise<void> {\n this._cached = null\n await this._localStorage.deleteState(this._localKey)\n }\n\n /**\n * Enter some scene\n */\n async enter<SceneState extends object, Scene extends Dispatcher<SceneState>>(\n scene: Scene,\n params?: {\n /**\n * Initial state for the scene\n *\n * Note that this will only work if the scene uses the same key delegate as this state.\n */\n with?: SceneState\n\n /** TTL for the scene (in seconds) */\n ttl?: number\n\n /**\n * If currently in a scoped scene, whether to reset the state\n *\n * @default true\n */\n reset?: boolean\n },\n ): Promise<void> {\n const { with: with_, ttl, reset = true } = params ?? {}\n\n if (reset && this._scoped) await this.delete()\n\n if (!scene['_scene']) {\n throw new MtArgumentError('Cannot enter a non-scene Dispatcher')\n }\n\n if (!scene['_parent']) {\n throw new MtArgumentError('This scene has not been registered')\n }\n\n this._scene = scene['_scene']\n this._scoped = scene['_sceneScoped']\n this._updateLocalKey()\n\n await this._storage.setCurrentScene(this._key, this._scene, ttl)\n\n if (with_) {\n if (scene['_customStateKeyDelegate']) {\n throw new MtArgumentError('Cannot use `with` parameter when the scene uses a custom state key delegate')\n }\n\n await scene.getState(this._key).set(with_, ttl)\n }\n }\n\n /**\n * Exit from current scene to the root\n *\n * @param reset\n * Whether to reset scene state (only applicable in case this is a scoped scene)\n */\n async exit(reset = true): Promise<void> {\n if (reset && this._scoped) await this.delete()\n this._scene = null\n this._updateLocalKey()\n await this._storage.deleteCurrentScene(this._key)\n }\n\n /**\n * Rate limit some handler.\n *\n * When the rate limit exceeds, {@link RateLimitError} is thrown.\n *\n * This is a simple rate-limiting solution that uses\n * the same key as the state. If you need something more\n * sophisticated and/or customizable, you'll have to implement\n * your own rate-limiter.\n *\n * > **Note**: `key` is used to prefix the local key\n * > derived using the given key delegate.\n *\n * @param key Key of the rate limit\n * @param limit Maximum number of requests in `window`\n * @param window Window size in seconds\n * @returns Tuple containing the number of remaining and\n * unix time in ms when the user can try again\n */\n async rateLimit(key: string, limit: number, window: number): Promise<[number, number]> {\n const [remaining, reset] = await this._localStorage.getRateLimit(`${key}:${this._localKey}`, limit, window)\n\n if (!remaining) {\n throw new RateLimitError(reset)\n }\n\n return [remaining - 1, reset]\n }\n\n /**\n * Throttle some handler.\n *\n * When the rate limit exceeds, this function waits for it to reset.\n *\n * This is a simple wrapper over {@link rateLimit}, and follows the same logic.\n *\n * > **Note**: `key` is used to prefix the local key\n * > derived using the given key delegate.\n *\n * @param key Key of the rate limit\n * @param limit Maximum number of requests in `window`\n * @param window Window size in seconds\n * @returns Tuple containing the number of remaining and\n * unix time in ms when the user can try again\n */\n async throttle(key: string, limit: number, window: number): Promise<[number, number]> {\n try {\n return await this.rateLimit(key, limit, window)\n } catch (e: unknown) {\n if (e instanceof RateLimitError) {\n await sleep(e.reset - Date.now())\n\n return this.throttle(key, limit, window)\n }\n\n throw e\n }\n }\n\n /**\n * Reset the rate limit\n *\n * @param key Key of the rate limit\n */\n async resetRateLimit(key: string): Promise<void> {\n await this._localStorage.resetRateLimit(`${key}:${this._localKey}`)\n }\n}\n"]}
@@ -0,0 +1,60 @@
1
+ import { MaybeAsync } from '@mtcute/client';
2
+ import { MessageContext } from './context/message.js';
3
+ import { Dispatcher } from './dispatcher.js';
4
+ import { filters } from './filters/index.js';
5
+ import { UpdateState } from './state/update-state.js';
6
+ /**
7
+ * Action for the wizard scene.
8
+ *
9
+ * `Next`: Continue to the next registered step
10
+ * (or exit, if this is the last step)
11
+ *
12
+ * `Stay`: Stay on the same step
13
+ *
14
+ * `Exit`: Exit from the wizard scene
15
+ *
16
+ * You can also return a `number` to jump to that step
17
+ */
18
+ export declare enum WizardSceneAction {
19
+ Next = "next",
20
+ Stay = "stay",
21
+ Exit = "exit"
22
+ }
23
+ interface WizardInternalState {
24
+ $step?: number;
25
+ }
26
+ /**
27
+ * Wizard is a special type of Dispatcher
28
+ * that can be used to simplify implementing
29
+ * step-by-step scenes.
30
+ */
31
+ export declare class WizardScene<State extends object> extends Dispatcher<State & WizardInternalState> {
32
+ private _steps;
33
+ private _defaultState;
34
+ setDefaultState(defaultState: State): void;
35
+ /**
36
+ * Get the total number of registered steps
37
+ */
38
+ get totalSteps(): number;
39
+ /**
40
+ * Go to the Nth step
41
+ */
42
+ goToStep(state: UpdateState<WizardInternalState>, step: number): Promise<void>;
43
+ /**
44
+ * Skip N steps
45
+ */
46
+ skip(state: UpdateState<WizardInternalState>, count?: number): Promise<void>;
47
+ /**
48
+ * Filter that will only pass if the current step is `step`
49
+ */
50
+ static onNthStep(step: number): filters.UpdateFilter<any, {}, WizardInternalState>;
51
+ /**
52
+ * Filter that will only pass if the current step is the one after last one added
53
+ */
54
+ onCurrentStep(): filters.UpdateFilter<any, {}, WizardInternalState>;
55
+ /**
56
+ * Add a step to the wizard
57
+ */
58
+ addStep(handler: (msg: MessageContext, state: UpdateState<State & WizardInternalState>) => MaybeAsync<WizardSceneAction | number>): void;
59
+ }
60
+ export {};