@mtcute/dispatcher 0.17.2 → 0.18.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 (111) hide show
  1. package/callback-data-builder.cjs +126 -0
  2. package/callback-data-builder.d.cts +49 -0
  3. package/callback-data-builder.js +121 -0
  4. package/callback-data-builder.test.d.cts +1 -0
  5. package/context/base.d.cts +9 -0
  6. package/context/business-message.cjs +143 -0
  7. package/context/business-message.d.cts +60 -0
  8. package/context/business-message.js +138 -0
  9. package/context/callback-query.cjs +92 -0
  10. package/context/callback-query.d.cts +62 -0
  11. package/context/callback-query.js +87 -0
  12. package/context/chat-join-request.cjs +32 -0
  13. package/context/chat-join-request.d.cts +17 -0
  14. package/context/chat-join-request.d.ts +1 -1
  15. package/context/chat-join-request.js +27 -0
  16. package/context/chosen-inline-result.cjs +30 -0
  17. package/context/chosen-inline-result.d.cts +22 -0
  18. package/context/chosen-inline-result.d.ts +1 -1
  19. package/context/chosen-inline-result.js +25 -0
  20. package/context/index.d.cts +9 -0
  21. package/context/inline-query.cjs +20 -0
  22. package/context/inline-query.d.cts +15 -0
  23. package/context/inline-query.js +15 -0
  24. package/context/message.cjs +182 -0
  25. package/context/message.d.cts +82 -0
  26. package/context/message.d.ts +2 -2
  27. package/context/message.js +177 -0
  28. package/context/parse.cjs +45 -0
  29. package/context/parse.d.cts +13 -0
  30. package/context/parse.js +40 -0
  31. package/context/pre-checkout-query.cjs +24 -0
  32. package/context/pre-checkout-query.d.cts +17 -0
  33. package/context/pre-checkout-query.d.ts +1 -1
  34. package/context/pre-checkout-query.js +19 -0
  35. package/context/scene-transition.cjs +52 -0
  36. package/context/scene-transition.d.cts +24 -0
  37. package/context/scene-transition.d.ts +1 -1
  38. package/context/scene-transition.js +47 -0
  39. package/dispatcher.cjs +860 -0
  40. package/dispatcher.d.cts +880 -0
  41. package/dispatcher.d.ts +4 -4
  42. package/dispatcher.js +855 -0
  43. package/filters/bots.cjs +94 -0
  44. package/filters/bots.d.cts +62 -0
  45. package/filters/bots.d.ts +2 -4
  46. package/filters/bots.js +89 -0
  47. package/filters/bots.test.d.cts +1 -0
  48. package/filters/bundle.cjs +79 -0
  49. package/filters/bundle.d.cts +10 -0
  50. package/filters/bundle.js +74 -0
  51. package/filters/chat.cjs +43 -0
  52. package/filters/chat.d.cts +29 -0
  53. package/filters/chat.d.ts +8 -6
  54. package/filters/chat.js +38 -0
  55. package/filters/group.cjs +49 -0
  56. package/filters/group.d.cts +26 -0
  57. package/filters/group.js +44 -0
  58. package/filters/index.d.cts +4 -0
  59. package/filters/logic.cjs +57 -0
  60. package/filters/logic.d.cts +29 -0
  61. package/filters/logic.js +52 -0
  62. package/filters/logic.test.d.cts +1 -0
  63. package/filters/message.cjs +130 -0
  64. package/filters/message.d.cts +223 -0
  65. package/filters/message.d.ts +5 -1
  66. package/filters/message.js +125 -0
  67. package/filters/state.cjs +21 -0
  68. package/filters/state.d.cts +15 -0
  69. package/filters/state.js +16 -0
  70. package/filters/text.cjs +87 -0
  71. package/filters/text.d.cts +64 -0
  72. package/filters/text.d.ts +2 -2
  73. package/filters/text.js +82 -0
  74. package/filters/types.d.cts +91 -0
  75. package/filters/updates.cjs +27 -0
  76. package/filters/updates.d.cts +39 -0
  77. package/filters/updates.js +22 -0
  78. package/filters/user.cjs +57 -0
  79. package/filters/user.d.cts +24 -0
  80. package/filters/user.js +52 -0
  81. package/handler.d.cts +41 -0
  82. package/handler.d.ts +1 -1
  83. package/index.cjs +37 -2528
  84. package/index.js +16 -2507
  85. package/package.json +10 -9
  86. package/propagation.cjs +15 -0
  87. package/propagation.d.cts +22 -0
  88. package/propagation.js +10 -0
  89. package/state/index.d.cts +5 -0
  90. package/state/key.cjs +32 -0
  91. package/state/key.d.cts +24 -0
  92. package/state/key.js +27 -0
  93. package/state/provider.d.cts +5 -0
  94. package/state/providers/index.d.cts +2 -0
  95. package/state/providers/memory.cjs +79 -0
  96. package/state/providers/memory.d.cts +29 -0
  97. package/state/providers/memory.js +74 -0
  98. package/state/providers/sqlite.cjs +99 -0
  99. package/state/providers/sqlite.d.cts +28 -0
  100. package/state/providers/sqlite.js +94 -0
  101. package/state/repository.d.cts +62 -0
  102. package/state/service.cjs +69 -0
  103. package/state/service.d.cts +20 -0
  104. package/state/service.js +64 -0
  105. package/state/update-state.cjs +206 -0
  106. package/state/update-state.d.cts +151 -0
  107. package/state/update-state.d.ts +1 -1
  108. package/state/update-state.js +201 -0
  109. package/wizard.cjs +90 -0
  110. package/wizard.d.cts +64 -0
  111. package/wizard.js +85 -0
package/package.json CHANGED
@@ -1,11 +1,15 @@
1
1
  {
2
2
  "name": "@mtcute/dispatcher",
3
3
  "type": "module",
4
- "version": "0.17.2",
4
+ "version": "0.18.0",
5
5
  "description": "Updates dispatcher and bot framework for @mtcute/client",
6
- "author": "alina sireneva <alina@tei.su>",
7
6
  "license": "MIT",
8
- "sideEffects": false,
7
+ "scripts": {},
8
+ "dependencies": {
9
+ "@mtcute/core": "^0.18.0",
10
+ "@fuman/utils": "0.0.4",
11
+ "events": "3.2.0"
12
+ },
9
13
  "exports": {
10
14
  ".": {
11
15
  "import": {
@@ -18,14 +22,11 @@
18
22
  }
19
23
  }
20
24
  },
21
- "scripts": {},
22
- "dependencies": {
23
- "@mtcute/core": "^0.17.1",
24
- "events": "3.2.0"
25
- },
25
+ "author": "alina sireneva <alina@tei.su>",
26
+ "sideEffects": false,
26
27
  "homepage": "https://mtcute.dev",
27
28
  "repository": {
28
29
  "type": "git",
29
- "url": "https://github.com/mtcute/mtcute"
30
+ "url": "git+https://github.com/mtcute/mtcute.git"
30
31
  }
31
32
  }
@@ -0,0 +1,15 @@
1
+ if (typeof globalThis !== "undefined" && !globalThis._MTCUTE_CJS_DEPRECATION_WARNED) {
2
+ globalThis._MTCUTE_CJS_DEPRECATION_WARNED = true;
3
+ console.warn("[mtcute-workspace] CommonJS support is deprecated and will be removed in 0.20.0. Please consider switching to ESM, it's " + (/* @__PURE__ */ new Date()).getFullYear() + " already.");
4
+ console.warn("[mtcute-workspace] Learn more about switching to ESM: https://gist.github.com/sindresorhus/a39789f98801d908bbc7ff3ecc99d99c");
5
+ }
6
+ "use strict";
7
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
8
+ var PropagationAction = /* @__PURE__ */ ((PropagationAction2) => {
9
+ PropagationAction2["Stop"] = "stop";
10
+ PropagationAction2["StopChildren"] = "stop-children";
11
+ PropagationAction2["Continue"] = "continue";
12
+ PropagationAction2["ToScene"] = "scene";
13
+ return PropagationAction2;
14
+ })(PropagationAction || {});
15
+ exports.PropagationAction = PropagationAction;
@@ -0,0 +1,22 @@
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
+ * or after `state.exit()` to dispatch the update to the root dispatcher.
16
+ */
17
+ export declare enum PropagationAction {
18
+ Stop = "stop",
19
+ StopChildren = "stop-children",
20
+ Continue = "continue",
21
+ ToScene = "scene"
22
+ }
package/propagation.js ADDED
@@ -0,0 +1,10 @@
1
+ var PropagationAction = /* @__PURE__ */ ((PropagationAction2) => {
2
+ PropagationAction2["Stop"] = "stop";
3
+ PropagationAction2["StopChildren"] = "stop-children";
4
+ PropagationAction2["Continue"] = "continue";
5
+ PropagationAction2["ToScene"] = "scene";
6
+ return PropagationAction2;
7
+ })(PropagationAction || {});
8
+ export {
9
+ PropagationAction
10
+ };
@@ -0,0 +1,5 @@
1
+ export * from './key.js';
2
+ export * from './provider.js';
3
+ export * from './providers/index.js';
4
+ export * from './repository.js';
5
+ export * from './update-state.js';
package/state/key.cjs ADDED
@@ -0,0 +1,32 @@
1
+ if (typeof globalThis !== "undefined" && !globalThis._MTCUTE_CJS_DEPRECATION_WARNED) {
2
+ globalThis._MTCUTE_CJS_DEPRECATION_WARNED = true;
3
+ console.warn("[mtcute-workspace] CommonJS support is deprecated and will be removed in 0.20.0. Please consider switching to ESM, it's " + (/* @__PURE__ */ new Date()).getFullYear() + " already.");
4
+ console.warn("[mtcute-workspace] Learn more about switching to ESM: https://gist.github.com/sindresorhus/a39789f98801d908bbc7ff3ecc99d99c");
5
+ }
6
+ "use strict";
7
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
8
+ const core = require("@mtcute/core");
9
+ const defaultStateKeyDelegate = (upd) => {
10
+ if ("type" in upd) {
11
+ return String(upd.id);
12
+ }
13
+ if (upd._name === "new_message" || upd._name === "new_business_message") {
14
+ if (upd.chat.type === "user") return String(upd.chat.id);
15
+ switch (upd.chat.chatType) {
16
+ case "channel":
17
+ return String(upd.chat.id);
18
+ case "group":
19
+ case "supergroup":
20
+ case "gigagroup":
21
+ return `${upd.chat.id}_${upd.sender.id}`;
22
+ default:
23
+ core.assertNever(upd.chat.chatType);
24
+ }
25
+ }
26
+ if (upd._name === "callback_query") {
27
+ if (upd.chat.type === "user") return `${upd.user.id}`;
28
+ return `${upd.chat.id}_${upd.user.id}`;
29
+ }
30
+ return null;
31
+ };
32
+ exports.defaultStateKeyDelegate = defaultStateKeyDelegate;
@@ -0,0 +1,24 @@
1
+ import { MaybePromise, Peer } from '@mtcute/core';
2
+ import { BusinessMessageContext } from '../context/business-message.js';
3
+ import { CallbackQueryContext, MessageContext } from '../context/index.js';
4
+ /**
5
+ * Function that determines how the state key is derived.
6
+ *
7
+ * The key is additionally prefixed with current scene, if any.
8
+ *
9
+ * @param msg Message or callback from which to derive the key
10
+ * @param scene Current scene UID, or `null` if none
11
+ */
12
+ export type StateKeyDelegate = (upd: MessageContext | BusinessMessageContext | CallbackQueryContext | Peer) => MaybePromise<string | null>;
13
+ /**
14
+ * Default state key delegate.
15
+ *
16
+ * Derives key as follows:
17
+ * - If private chat, `msg.chat.id`
18
+ * - If group chat, `msg.chat.id + '_' + msg.sender.id`
19
+ * - If channel, `msg.chat.id`
20
+ * - If non-inline callback query:
21
+ * - If in private chat (i.e. `upd.chatType === 'user'`), `upd.user.id`
22
+ * - If in group/channel/supergroup (i.e. `upd.chatType !== 'user'`), `upd.chatId + '_' + upd.user.id`
23
+ */
24
+ export declare const defaultStateKeyDelegate: StateKeyDelegate;
package/state/key.js ADDED
@@ -0,0 +1,27 @@
1
+ import { assertNever } from "@mtcute/core";
2
+ const defaultStateKeyDelegate = (upd) => {
3
+ if ("type" in upd) {
4
+ return String(upd.id);
5
+ }
6
+ if (upd._name === "new_message" || upd._name === "new_business_message") {
7
+ if (upd.chat.type === "user") return String(upd.chat.id);
8
+ switch (upd.chat.chatType) {
9
+ case "channel":
10
+ return String(upd.chat.id);
11
+ case "group":
12
+ case "supergroup":
13
+ case "gigagroup":
14
+ return `${upd.chat.id}_${upd.sender.id}`;
15
+ default:
16
+ assertNever(upd.chat.chatType);
17
+ }
18
+ }
19
+ if (upd._name === "callback_query") {
20
+ if (upd.chat.type === "user") return `${upd.user.id}`;
21
+ return `${upd.chat.id}_${upd.user.id}`;
22
+ }
23
+ return null;
24
+ };
25
+ export {
26
+ defaultStateKeyDelegate
27
+ };
@@ -0,0 +1,5 @@
1
+ import { IStorageProvider } from '@mtcute/core';
2
+ import { IStateRepository } from './repository.js';
3
+ export type IStateStorageProvider = IStorageProvider<{
4
+ state: IStateRepository;
5
+ }>;
@@ -0,0 +1,2 @@
1
+ export * from './memory.js';
2
+ export * from './sqlite.js';
@@ -0,0 +1,79 @@
1
+ if (typeof globalThis !== "undefined" && !globalThis._MTCUTE_CJS_DEPRECATION_WARNED) {
2
+ globalThis._MTCUTE_CJS_DEPRECATION_WARNED = true;
3
+ console.warn("[mtcute-workspace] CommonJS support is deprecated and will be removed in 0.20.0. Please consider switching to ESM, it's " + (/* @__PURE__ */ new Date()).getFullYear() + " already.");
4
+ console.warn("[mtcute-workspace] Learn more about switching to ESM: https://gist.github.com/sindresorhus/a39789f98801d908bbc7ff3ecc99d99c");
5
+ }
6
+ "use strict";
7
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
8
+ const core = require("@mtcute/core");
9
+ class MemoryStateRepository {
10
+ constructor(_driver) {
11
+ this._driver = _driver;
12
+ this.state = this._driver.getState("dispatcher_fsm", () => /* @__PURE__ */ new Map());
13
+ this.rl = this._driver.getState("rl", () => /* @__PURE__ */ new Map());
14
+ }
15
+ state;
16
+ rl;
17
+ setState(key, state, ttl) {
18
+ this.state.set(key, {
19
+ value: state,
20
+ expiresAt: ttl ? Date.now() + ttl * 1e3 : void 0
21
+ });
22
+ }
23
+ getState(key, now) {
24
+ const state = this.state.get(key);
25
+ if (!state) return null;
26
+ if (state.expiresAt && state.expiresAt < now) {
27
+ this.state.delete(key);
28
+ return null;
29
+ }
30
+ return state.value;
31
+ }
32
+ deleteState(key) {
33
+ this.state.delete(key);
34
+ }
35
+ vacuum(now) {
36
+ for (const [key, state] of this.state.entries()) {
37
+ if (state.expiresAt && state.expiresAt < now) {
38
+ this.state.delete(key);
39
+ }
40
+ }
41
+ for (const [key, state] of this.rl.entries()) {
42
+ if (state.reset < now) {
43
+ this.rl.delete(key);
44
+ }
45
+ }
46
+ }
47
+ getRateLimit(key, now, limit, window) {
48
+ const item = this.rl.get(key);
49
+ if (!item) {
50
+ const state = {
51
+ reset: now + window * 1e3,
52
+ remaining: limit
53
+ };
54
+ this.rl.set(key, state);
55
+ return [state.remaining, state.reset];
56
+ }
57
+ if (item.reset < now) {
58
+ const state = {
59
+ reset: now + window * 1e3,
60
+ remaining: limit
61
+ };
62
+ this.rl.set(key, state);
63
+ return [state.remaining, state.reset];
64
+ }
65
+ item.remaining = item.remaining > 0 ? item.remaining - 1 : 0;
66
+ return [item.remaining, item.reset];
67
+ }
68
+ resetRateLimit(key) {
69
+ this.rl.delete(key);
70
+ }
71
+ }
72
+ class MemoryStateStorage {
73
+ constructor(driver = new core.MemoryStorageDriver()) {
74
+ this.driver = driver;
75
+ this.state = new MemoryStateRepository(this.driver);
76
+ }
77
+ state;
78
+ }
79
+ exports.MemoryStateStorage = MemoryStateStorage;
@@ -0,0 +1,29 @@
1
+ import { MaybePromise, MemoryStorageDriver } from '@mtcute/core';
2
+ import { IStateStorageProvider } from '../provider.js';
3
+ import { IStateRepository } from '../repository.js';
4
+ interface StateDto {
5
+ value: string;
6
+ expiresAt?: number;
7
+ }
8
+ interface RateLimitDto {
9
+ reset: number;
10
+ remaining: number;
11
+ }
12
+ declare class MemoryStateRepository implements IStateRepository {
13
+ readonly _driver: MemoryStorageDriver;
14
+ readonly state: Map<string, StateDto>;
15
+ readonly rl: Map<string, RateLimitDto>;
16
+ constructor(_driver: MemoryStorageDriver);
17
+ setState(key: string, state: string, ttl?: number | undefined): void;
18
+ getState(key: string, now: number): string | null;
19
+ deleteState(key: string): void;
20
+ vacuum(now: number): void;
21
+ getRateLimit(key: string, now: number, limit: number, window: number): [number, number];
22
+ resetRateLimit(key: string): MaybePromise<void>;
23
+ }
24
+ export declare class MemoryStateStorage implements IStateStorageProvider {
25
+ readonly driver: MemoryStorageDriver;
26
+ readonly state: MemoryStateRepository;
27
+ constructor(driver?: MemoryStorageDriver);
28
+ }
29
+ export {};
@@ -0,0 +1,74 @@
1
+ import { MemoryStorageDriver } from "@mtcute/core";
2
+ class MemoryStateRepository {
3
+ constructor(_driver) {
4
+ this._driver = _driver;
5
+ this.state = this._driver.getState("dispatcher_fsm", () => /* @__PURE__ */ new Map());
6
+ this.rl = this._driver.getState("rl", () => /* @__PURE__ */ new Map());
7
+ }
8
+ state;
9
+ rl;
10
+ setState(key, state, ttl) {
11
+ this.state.set(key, {
12
+ value: state,
13
+ expiresAt: ttl ? Date.now() + ttl * 1e3 : void 0
14
+ });
15
+ }
16
+ getState(key, now) {
17
+ const state = this.state.get(key);
18
+ if (!state) return null;
19
+ if (state.expiresAt && state.expiresAt < now) {
20
+ this.state.delete(key);
21
+ return null;
22
+ }
23
+ return state.value;
24
+ }
25
+ deleteState(key) {
26
+ this.state.delete(key);
27
+ }
28
+ vacuum(now) {
29
+ for (const [key, state] of this.state.entries()) {
30
+ if (state.expiresAt && state.expiresAt < now) {
31
+ this.state.delete(key);
32
+ }
33
+ }
34
+ for (const [key, state] of this.rl.entries()) {
35
+ if (state.reset < now) {
36
+ this.rl.delete(key);
37
+ }
38
+ }
39
+ }
40
+ getRateLimit(key, now, limit, window) {
41
+ const item = this.rl.get(key);
42
+ if (!item) {
43
+ const state = {
44
+ reset: now + window * 1e3,
45
+ remaining: limit
46
+ };
47
+ this.rl.set(key, state);
48
+ return [state.remaining, state.reset];
49
+ }
50
+ if (item.reset < now) {
51
+ const state = {
52
+ reset: now + window * 1e3,
53
+ remaining: limit
54
+ };
55
+ this.rl.set(key, state);
56
+ return [state.remaining, state.reset];
57
+ }
58
+ item.remaining = item.remaining > 0 ? item.remaining - 1 : 0;
59
+ return [item.remaining, item.reset];
60
+ }
61
+ resetRateLimit(key) {
62
+ this.rl.delete(key);
63
+ }
64
+ }
65
+ class MemoryStateStorage {
66
+ constructor(driver = new MemoryStorageDriver()) {
67
+ this.driver = driver;
68
+ this.state = new MemoryStateRepository(this.driver);
69
+ }
70
+ state;
71
+ }
72
+ export {
73
+ MemoryStateStorage
74
+ };
@@ -0,0 +1,99 @@
1
+ if (typeof globalThis !== "undefined" && !globalThis._MTCUTE_CJS_DEPRECATION_WARNED) {
2
+ globalThis._MTCUTE_CJS_DEPRECATION_WARNED = true;
3
+ console.warn("[mtcute-workspace] CommonJS support is deprecated and will be removed in 0.20.0. Please consider switching to ESM, it's " + (/* @__PURE__ */ new Date()).getFullYear() + " already.");
4
+ console.warn("[mtcute-workspace] Learn more about switching to ESM: https://gist.github.com/sindresorhus/a39789f98801d908bbc7ff3ecc99d99c");
5
+ }
6
+ "use strict";
7
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
8
+ class SqliteStateRepository {
9
+ constructor(_driver) {
10
+ this._driver = _driver;
11
+ _driver.registerMigration("state", 1, (db) => {
12
+ db.exec(`
13
+ create table fsm_state (
14
+ key text primary key,
15
+ value text not null,
16
+ expires_at integer
17
+ );
18
+ create table rl_state (
19
+ key text primary key,
20
+ reset integer not null,
21
+ remaining integer not null
22
+ );
23
+ `);
24
+ });
25
+ _driver.onLoad(() => {
26
+ this._setState = _driver.db.prepare(
27
+ "insert or replace into fsm_state (key, value, expires_at) values (?, ?, ?)"
28
+ );
29
+ this._getState = _driver.db.prepare("select value, expires_at from fsm_state where key = ?");
30
+ this._deleteState = _driver.db.prepare("delete from fsm_state where key = ?");
31
+ this._deleteOldState = _driver.db.prepare("delete from fsm_state where expires_at < ?");
32
+ this._setRl = _driver.db.prepare("insert or replace into rl_state (key, reset, remaining) values (?, ?, ?)");
33
+ this._getRl = _driver.db.prepare("select reset, remaining from rl_state where key = ?");
34
+ this._deleteRl = _driver.db.prepare("delete from rl_state where key = ?");
35
+ this._deleteOldRl = _driver.db.prepare("delete from rl_state where reset < ?");
36
+ });
37
+ _driver.registerLegacyMigration("state", (db) => {
38
+ db.exec("drop table state");
39
+ });
40
+ }
41
+ _setState;
42
+ setState(key, state, ttl) {
43
+ this._setState.run(key, state, ttl ? Date.now() + ttl * 1e3 : void 0);
44
+ }
45
+ _getState;
46
+ getState(key, now) {
47
+ const res_ = this._getState.get(key);
48
+ if (!res_) return null;
49
+ const res = res_;
50
+ if (res.expires_at && res.expires_at < now) {
51
+ this._deleteState.run(key);
52
+ return null;
53
+ }
54
+ return res.value;
55
+ }
56
+ _deleteState;
57
+ deleteState(key) {
58
+ this._deleteState.run(key);
59
+ }
60
+ _deleteOldState;
61
+ _deleteOldRl;
62
+ vacuum(now) {
63
+ this._deleteOldState.run(now);
64
+ this._deleteOldRl.run(now);
65
+ }
66
+ _setRl;
67
+ _getRl;
68
+ _deleteRl;
69
+ getRateLimit(key, now, limit, window) {
70
+ const val = this._getRl.get(key);
71
+ if (!val || val.reset < now) {
72
+ const item = {
73
+ reset: now + window * 1e3,
74
+ remaining: limit
75
+ };
76
+ this._setRl.run(key, item.reset, item.remaining);
77
+ return [item.remaining, item.reset];
78
+ }
79
+ if (val.remaining > 0) {
80
+ val.remaining -= 1;
81
+ this._setRl.run(key, val.reset, val.remaining);
82
+ }
83
+ return [val.remaining, val.reset];
84
+ }
85
+ resetRateLimit(key) {
86
+ this._deleteRl.run(key);
87
+ }
88
+ }
89
+ class SqliteStateStorage {
90
+ constructor(driver) {
91
+ this.driver = driver;
92
+ this.state = new SqliteStateRepository(driver);
93
+ }
94
+ state;
95
+ static from(provider) {
96
+ return new SqliteStateStorage(provider.driver);
97
+ }
98
+ }
99
+ exports.SqliteStateStorage = SqliteStateStorage;
@@ -0,0 +1,28 @@
1
+ import { BaseSqliteStorage, BaseSqliteStorageDriver, MaybePromise } from '@mtcute/core';
2
+ import { IStateStorageProvider } from '../provider.js';
3
+ import { IStateRepository } from '../repository.js';
4
+ declare class SqliteStateRepository implements IStateRepository {
5
+ readonly _driver: BaseSqliteStorageDriver;
6
+ constructor(_driver: BaseSqliteStorageDriver);
7
+ private _setState;
8
+ setState(key: string, state: string, ttl?: number | undefined): MaybePromise<void>;
9
+ private _getState;
10
+ getState(key: string, now: number): MaybePromise<string | null>;
11
+ private _deleteState;
12
+ deleteState(key: string): MaybePromise<void>;
13
+ private _deleteOldState;
14
+ private _deleteOldRl;
15
+ vacuum(now: number): MaybePromise<void>;
16
+ private _setRl;
17
+ private _getRl;
18
+ private _deleteRl;
19
+ getRateLimit(key: string, now: number, limit: number, window: number): [number, number];
20
+ resetRateLimit(key: string): MaybePromise<void>;
21
+ }
22
+ export declare class SqliteStateStorage implements IStateStorageProvider {
23
+ readonly driver: BaseSqliteStorageDriver;
24
+ readonly state: SqliteStateRepository;
25
+ constructor(driver: BaseSqliteStorageDriver);
26
+ static from(provider: BaseSqliteStorage): SqliteStateStorage;
27
+ }
28
+ export {};
@@ -0,0 +1,94 @@
1
+ class SqliteStateRepository {
2
+ constructor(_driver) {
3
+ this._driver = _driver;
4
+ _driver.registerMigration("state", 1, (db) => {
5
+ db.exec(`
6
+ create table fsm_state (
7
+ key text primary key,
8
+ value text not null,
9
+ expires_at integer
10
+ );
11
+ create table rl_state (
12
+ key text primary key,
13
+ reset integer not null,
14
+ remaining integer not null
15
+ );
16
+ `);
17
+ });
18
+ _driver.onLoad(() => {
19
+ this._setState = _driver.db.prepare(
20
+ "insert or replace into fsm_state (key, value, expires_at) values (?, ?, ?)"
21
+ );
22
+ this._getState = _driver.db.prepare("select value, expires_at from fsm_state where key = ?");
23
+ this._deleteState = _driver.db.prepare("delete from fsm_state where key = ?");
24
+ this._deleteOldState = _driver.db.prepare("delete from fsm_state where expires_at < ?");
25
+ this._setRl = _driver.db.prepare("insert or replace into rl_state (key, reset, remaining) values (?, ?, ?)");
26
+ this._getRl = _driver.db.prepare("select reset, remaining from rl_state where key = ?");
27
+ this._deleteRl = _driver.db.prepare("delete from rl_state where key = ?");
28
+ this._deleteOldRl = _driver.db.prepare("delete from rl_state where reset < ?");
29
+ });
30
+ _driver.registerLegacyMigration("state", (db) => {
31
+ db.exec("drop table state");
32
+ });
33
+ }
34
+ _setState;
35
+ setState(key, state, ttl) {
36
+ this._setState.run(key, state, ttl ? Date.now() + ttl * 1e3 : void 0);
37
+ }
38
+ _getState;
39
+ getState(key, now) {
40
+ const res_ = this._getState.get(key);
41
+ if (!res_) return null;
42
+ const res = res_;
43
+ if (res.expires_at && res.expires_at < now) {
44
+ this._deleteState.run(key);
45
+ return null;
46
+ }
47
+ return res.value;
48
+ }
49
+ _deleteState;
50
+ deleteState(key) {
51
+ this._deleteState.run(key);
52
+ }
53
+ _deleteOldState;
54
+ _deleteOldRl;
55
+ vacuum(now) {
56
+ this._deleteOldState.run(now);
57
+ this._deleteOldRl.run(now);
58
+ }
59
+ _setRl;
60
+ _getRl;
61
+ _deleteRl;
62
+ getRateLimit(key, now, limit, window) {
63
+ const val = this._getRl.get(key);
64
+ if (!val || val.reset < now) {
65
+ const item = {
66
+ reset: now + window * 1e3,
67
+ remaining: limit
68
+ };
69
+ this._setRl.run(key, item.reset, item.remaining);
70
+ return [item.remaining, item.reset];
71
+ }
72
+ if (val.remaining > 0) {
73
+ val.remaining -= 1;
74
+ this._setRl.run(key, val.reset, val.remaining);
75
+ }
76
+ return [val.remaining, val.reset];
77
+ }
78
+ resetRateLimit(key) {
79
+ this._deleteRl.run(key);
80
+ }
81
+ }
82
+ class SqliteStateStorage {
83
+ constructor(driver) {
84
+ this.driver = driver;
85
+ this.state = new SqliteStateRepository(driver);
86
+ }
87
+ state;
88
+ static from(provider) {
89
+ return new SqliteStateStorage(provider.driver);
90
+ }
91
+ }
92
+ export {
93
+ SqliteStateStorage
94
+ };
@@ -0,0 +1,62 @@
1
+ import { MaybePromise } from '@mtcute/core';
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 IStateRepository {
15
+ /**
16
+ * Retrieve state from the storage
17
+ * If state is not found or has expired, return `null`
18
+ *
19
+ * @param key Key of the state, as defined by {@link StateKeyDelegate}
20
+ */
21
+ getState: (key: string, now: number) => MaybePromise<string | null>;
22
+ /**
23
+ * Save state to the storage
24
+ *
25
+ * @param key Key of the state, as defined by {@link StateKeyDelegate}
26
+ * @param state String representing the state
27
+ * @param ttl TTL for the state, in seconds
28
+ */
29
+ setState: (key: string, state: string, ttl?: number) => MaybePromise<void>;
30
+ /**
31
+ * Delete state from the storage
32
+ *
33
+ * @param key Key of the state, as defined by {@link StateKeyDelegate}
34
+ */
35
+ deleteState: (key: string) => MaybePromise<void>;
36
+ /**
37
+ * Clean up expired states and rate limits.
38
+ *
39
+ * @param now Current unix time in ms
40
+ */
41
+ vacuum: (now: number) => MaybePromise<void>;
42
+ /**
43
+ * Get information about a rate limit.
44
+ *
45
+ * It is recommended that you use sliding window or leaky bucket
46
+ * to implement rate limiting ([learn more](https://konghq.com/blog/how-to-design-a-scalable-rate-limiting-algorithm/)),
47
+ *
48
+ * @param key Key of the rate limit
49
+ * @param now Current unix time in ms
50
+ * @param limit Maximum number of requests in `window`
51
+ * @param window Window size in seconds
52
+ * @returns Tuple containing the number of remaining and
53
+ * unix time in ms when the user can try again
54
+ */
55
+ getRateLimit: (key: string, now: number, limit: number, window: number) => MaybePromise<[number, number]>;
56
+ /**
57
+ * Reset a rate limit.
58
+ *
59
+ * @param key Key of the rate limit
60
+ */
61
+ resetRateLimit: (key: string) => MaybePromise<void>;
62
+ }